[CalendarServer-changes] [6186] CalendarServer/branches/generic-sqlstore
source_changes at macosforge.org
source_changes at macosforge.org
Wed Aug 25 18:34:39 PDT 2010
Revision: 6186
http://trac.macosforge.org/projects/calendarserver/changeset/6186
Author: cdaboo at apple.com
Date: 2010-08-25 18:34:38 -0700 (Wed, 25 Aug 2010)
Log Message:
-----------
Push txcaldav and txcarddav modules into txdav and rename for consistency.
Modified Paths:
--------------
CalendarServer/branches/generic-sqlstore/pyflakes
CalendarServer/branches/generic-sqlstore/setup.py
CalendarServer/branches/generic-sqlstore/support/Makefile.Apple
CalendarServer/branches/generic-sqlstore/test
CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py
CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py
CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py
CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py
CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py
CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py
CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py
CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.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/sql_legacy.py
CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py
CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py
CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py
Added Paths:
-----------
CalendarServer/branches/generic-sqlstore/txdav/caldav/
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py
Removed Paths:
-------------
CalendarServer/branches/generic-sqlstore/txcaldav/
CalendarServer/branches/generic-sqlstore/txcarddav/
CalendarServer/branches/generic-sqlstore/txdav/caldav/calendarstore/
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py
CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/addressbookstore/
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py
Modified: CalendarServer/branches/generic-sqlstore/pyflakes
===================================================================
--- CalendarServer/branches/generic-sqlstore/pyflakes 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/pyflakes 2010-08-26 01:34:38 UTC (rev 6186)
@@ -9,7 +9,7 @@
export PYTHONPATH="${flakes}:${PYTHONPATH:-}";
if [ $# -eq 0 ]; then
- set - calendarserver twistedcaldav twext txdav txcaldav txcarddav;
+ set - calendarserver twistedcaldav twext txdav;
fi;
cd "${wd}" && "${flakes}/bin/pyflakes" "$@" | sed \
Modified: CalendarServer/branches/generic-sqlstore/setup.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/setup.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/setup.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -108,7 +108,7 @@
"zoneinfo/*/*/*.ics",
"images/*/*.jpg",
],
- "txcaldav.calendarstore": [
+ "txdav.common.datastore": [
"*.sql",
],
},
Modified: CalendarServer/branches/generic-sqlstore/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/generic-sqlstore/support/Makefile.Apple 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/support/Makefile.Apple 2010-08-26 01:34:38 UTC (rev 6186)
@@ -127,7 +127,7 @@
$(BuildDirectory)/$(Project):
@echo "Copying source for $(Project)..."
$(_v) $(MKDIR) -p "$@"
- $(_v) pax -rw bin conf Makefile lib-patches setup.py calendarserver twistedcaldav twext txdav txcaldav txcarddav twisted support "$@/"
+ $(_v) pax -rw bin conf Makefile lib-patches setup.py calendarserver twistedcaldav twext txdav twisted support "$@/"
$(BuildDirectory)/%: %.tgz
@echo "Extracting source for $(notdir $<)..."
Modified: CalendarServer/branches/generic-sqlstore/test
===================================================================
--- CalendarServer/branches/generic-sqlstore/test 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/test 2010-08-26 01:34:38 UTC (rev 6186)
@@ -78,7 +78,7 @@
if [ $# -gt 0 ]; then
test_modules="$@";
else
- test_modules="calendarserver twistedcaldav twext txdav txcaldav txcarddav ${m_twisted}";
+ test_modules="calendarserver twistedcaldav twext txdav ${m_twisted}";
fi;
cd "${wd}" && "${python}" "${trial}" --rterrors ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};
Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/resource.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -240,14 +240,14 @@
def associateWithTransaction(self, transaction):
"""
- Associate this resource with a L{txcaldav.idav.ITransaction}; when this
+ Associate this resource with a L{txdav.caldav.idav.ITransaction}; when this
resource (or any of its children) are rendered successfully, commit the
transaction. Otherwise, abort the transaction.
@param transaction: the transaction to associate this resource and its
children with.
- @type transaction: L{txcaldav.idav.ITransaction}
+ @type transaction: L{txdav.caldav.idav.ITransaction}
"""
# FIXME: needs to reject association with transaction if it's already
# got one (resources associated with a transaction are not reusable)
Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/storebridge.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -16,8 +16,8 @@
##
"""
-Wrappers to translate between the APIs in L{txcaldav.icalendarstore} and
-L{txcarddav.iaddressbookstore} and those in L{twistedcaldav}.
+Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
+L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
"""
import hashlib
@@ -206,10 +206,10 @@
Initialize with a calendar.
@param calendar: the wrapped calendar.
- @type calendar: L{txcaldav.icalendarstore.ICalendar}
+ @type calendar: L{txdav.caldav.icalendarstore.ICalendar}
@param home: the home through which the given calendar was accessed.
- @type home: L{txcaldav.icalendarstore.ICalendarHome}
+ @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
"""
self._newStoreCalendar = calendar
self._newStoreParentHome = home
@@ -664,12 +664,12 @@
class CalendarCollectionResource(_CalendarChildHelper, CalDAVResource):
"""
- Wrapper around a L{txcaldav.icalendar.ICalendar}.
+ Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
"""
def __init__(self, calendar, home, *args, **kw):
"""
- Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+ Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
and the arguments required for L{CalDAVResource}.
"""
super(CalendarCollectionResource, self).__init__(*args, **kw)
@@ -882,7 +882,7 @@
@param home: The calendar home which will be this resource's parent,
when it exists.
- @type home: L{txcaldav.icalendarstore.ICalendarHome}
+ @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
"""
super(ProtoCalendarCollectionResource, self).__init__(*args, **kw)
self._newStoreParentHome = home
@@ -957,7 +957,7 @@
Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
@param calendarObject: The storage for the calendar object.
- @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
+ @type calendarObject: L{txdav.caldav.icalendarstore.ICalendarObject}
"""
super(CalendarObjectResource, self).__init__(*args, **kw)
self._initializeWithObject(calendarObject)
@@ -1221,10 +1221,10 @@
Initialize with a addressbook.
@param addressbook: the wrapped addressbook.
- @type addressbook: L{txcarddav.iaddressbookstore.IAddressBook}
+ @type addressbook: L{txdav.carddav.iaddressbookstore.IAddressBook}
@param home: the home through which the given addressbook was accessed.
- @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
+ @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
"""
self._newStoreAddressBook = addressbook
self._newStoreParentHome = home
@@ -1307,12 +1307,12 @@
class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
"""
- Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
+ Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
"""
def __init__(self, addressbook, home, *args, **kw):
"""
- Create a AddressBookCollectionResource from a L{txcarddav.iaddressbook.IAddressBook}
+ Create a AddressBookCollectionResource from a L{txdav.carddav.iaddressbook.IAddressBook}
and the arguments required for L{CalDAVResource}.
"""
super(AddressBookCollectionResource, self).__init__(*args, **kw)
@@ -1486,7 +1486,7 @@
@param home: The addressbook home which will be this resource's parent,
when it exists.
- @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
+ @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
"""
super(ProtoAddressBookCollectionResource, self).__init__(*args, **kw)
self._newStoreParentHome = home
@@ -1552,7 +1552,7 @@
class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
"""
- Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
+ Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
"""
pass
@@ -1575,7 +1575,7 @@
Construct a L{AddressBookObjectResource} from an L{IAddressBookObject}.
@param Object: The storage for the addressbook object.
- @type Object: L{txcarddav.iaddressbookstore.IAddressBookObject}
+ @type Object: L{txdav.carddav.iaddressbookstore.IAddressBookObject}
"""
super(AddressBookObjectResource, self).__init__(*args, **kw)
self._initializeWithObject(Object)
@@ -1812,12 +1812,12 @@
class StoreNotificationCollectionResource(_NotificationChildHelper,
NotificationCollectionResource):
"""
- Wrapper around a L{txcaldav.icalendar.ICalendar}.
+ Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
"""
def __init__(self, notifications, home, *args, **kw):
"""
- Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+ Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
and the arguments required for L{CalDAVResource}.
"""
super(StoreNotificationCollectionResource, self).__init__(*args, **kw)
@@ -1855,7 +1855,7 @@
@param home: The calendar home which will be this resource's parent,
when it exists.
- @type home: L{txcaldav.icalendarstore.ICalendarHome}
+ @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
"""
self._newStoreParentHome = home
super(StoreProtoNotificationCollectionResource, self).__init__(*args, **kw)
@@ -1925,7 +1925,7 @@
Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
@param calendarObject: The storage for the calendar object.
- @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
+ @type calendarObject: L{txdav.caldav.icalendarstore.ICalendarObject}
"""
super(StoreNotificationObjectFile, self).__init__(*args, **kw)
self._initializeWithObject(notificationObject)
Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_calendarquery.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -34,8 +34,8 @@
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase
from twisted.internet.defer import inlineCallbacks, returnValue
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory
@inlineCallbacks
Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_sharing.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -25,8 +25,8 @@
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase, norequest
from twistedcaldav.resource import CalDAVResource
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory
class SharingTests(HomeTestCase):
Modified: CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/twistedcaldav/test/test_wrapping.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -37,15 +37,15 @@
from twistedcaldav.test.util import TestCase
-from txcaldav.calendarstore.test.test_file import event4_text
+from txdav.caldav.datastore.test.test_file import event4_text
-from txcarddav.addressbookstore.test.test_file import vcard4_text
+from txdav.carddav.datastore.test.test_file import vcard4_text
-from txcaldav.calendarstore.test.test_postgres import buildStore
-from txcaldav.calendarstore.test.common import StubNotifierFactory, \
+from txdav.caldav.datastore.test.test_postgres import buildStore
+from txdav.caldav.datastore.test.common import StubNotifierFactory, \
assertProvides
-from txcaldav.icalendarstore import ICalendarHome
-from txcarddav.iaddressbookstore import IAddressBookHome
+from txdav.caldav.icalendarstore import ICalendarHome
+from txdav.carddav.iaddressbookstore import IAddressBookHome
@@ -73,7 +73,7 @@
class WrappingTests(TestCase):
"""
Tests for L{twistedcaldav.static.CalDAVResource} creating the appropriate type
- of txcaldav.calendarstore.file underlying object when it can determine what
+ of txdav.caldav.datastore.file underlying object when it can determine what
type it really represents.
"""
Modified: CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/datastore/util.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/test/test_sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -15,8 +15,8 @@
##
"""
-Tests for txcaldav.calendarstore.postgres, mostly based on
-L{txcaldav.calendarstore.test.common}.
+Tests for txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
"""
@@ -28,7 +28,7 @@
from twisted.python import log
from txdav.common.datastore.sql import v1_schema
-from txcaldav.calendarstore.test.common import StubNotifierFactory
+from txdav.caldav.datastore.test.common import StubNotifierFactory
from txdav.common.datastore.sql import CommonDataStore
from txdav.base.datastore.subpostgres import PostgresService
Modified: CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/base/propertystore/xattr.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txdav.base.propertystore.test.test_xattr,txcaldav.calendarstore,txcarddav.addressbookstore -*-
+# -*- test-case-name: txdav.base.propertystore.test.test_xattr,txdav.caldav.datastore,txdav.carddav.datastore -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,542 +0,0 @@
-# -*- 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.
-##
-
-"""
-File calendar store.
-"""
-
-__all__ = [
- "CalendarStore",
- "CalendarStoreTransaction",
- "CalendarHome",
- "Calendar",
- "CalendarObject",
-]
-
-import hashlib
-
-from errno import ENOENT
-
-from twisted.internet.interfaces import ITransport
-from twisted.python.failure import Failure
-
-from txdav.propertystore.xattr import PropertyStore
-
-from twext.python.vcomponent import InvalidICalendarDataError
-from twext.python.vcomponent import VComponent
-from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
-from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.http_headers import generateContentType
-
-from twistedcaldav import caldavxml, customxml
-from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
-from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
-from twistedcaldav.sharing import InvitesDatabase
-
-from txcaldav.icalendarstore import IAttachment
-from txcaldav.icalendarstore import ICalendar, ICalendarObject
-from txcaldav.icalendarstore import ICalendarHome
-
-from txcaldav.calendarstore.util import (
- validateCalendarComponent, dropboxIDFromCalendarObject
-)
-
-from txdav.common.datastore.file import (
- CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
- CommonObjectResource
-, CommonStubResource)
-
-from txdav.common.icommondatastore import (NoSuchObjectResourceError,
- InternalDataStoreError)
-from txdav.datastore.file import writeOperation, hidden, FileMetaDataMixin
-from txdav.propertystore.base import PropertyName
-
-from zope.interface import implements
-
-CalendarStore = CommonDataStore
-
-CalendarStoreTransaction = CommonStoreTransaction
-
-class CalendarHome(CommonHome):
- implements(ICalendarHome)
-
- def __init__(self, uid, path, calendarStore, transaction, notifier):
- super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
-
- self._childClass = Calendar
-
-
- def calendarWithName(self, name):
- if name in ('dropbox', 'notifications', 'freebusy'):
- # "dropbox" is a file storage area, not a calendar.
- return None
- else:
- return self.childWithName(name)
-
-
- createCalendarWithName = CommonHome.createChildWithName
- removeCalendarWithName = CommonHome.removeChildWithName
-
- def calendars(self):
- """
- Return a generator of the child resource objects.
- """
- for child in self.children():
- if child.name() in ('dropbox', 'notification'):
- continue
- yield child
-
- def listCalendars(self):
- """
- Return a generator of the child resource names.
- """
- for name in self.listChildren():
- if name in ('dropbox', 'notification'):
- continue
- yield name
-
-
- 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
-
-
- @property
- def _calendarStore(self):
- return self._dataStore
-
-
- def created(self):
- self.createCalendarWithName("calendar")
- defaultCal = self.calendarWithName("calendar")
- props = defaultCal.properties()
- props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
- Opaque())
- self.createCalendarWithName("inbox")
-
-
-
-class Calendar(CommonHomeChild):
- """
- File-based implementation of L{ICalendar}.
- """
- implements(ICalendar)
-
- def __init__(self, name, calendarHome, notifier, realName=None):
- """
- Initialize a calendar pointing at a path on disk.
-
- @param name: the subdirectory of calendarHome where this calendar
- resides.
- @type name: C{str}
-
- @param calendarHome: the home containing this calendar.
- @type calendarHome: L{CalendarHome}
-
- @param realName: If this calendar was just created, the name which it
- will eventually have on disk.
- @type realName: C{str}
- """
- super(Calendar, self).__init__(name, calendarHome, notifier,
- realName=realName)
-
- self._index = Index(self)
- self._invites = Invites(self)
- self._objectResourceClass = CalendarObject
-
-
- @property
- def _calendarHome(self):
- return self._home
-
-
- def resourceType(self):
- return ResourceType.calendar #@UndefinedVariable
-
-
- ownerCalendarHome = CommonHomeChild.ownerHome
- calendarObjects = CommonHomeChild.objectResources
- listCalendarObjects = CommonHomeChild.listObjectResources
- calendarObjectWithName = CommonHomeChild.objectResourceWithName
- calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
- createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
- removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
- removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
- calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
-
-
- def calendarObjectsInTimeRange(self, start, end, timeZone):
- raise NotImplementedError()
-
-
- def initPropertyStore(self, props):
- # Setup peruser special properties
- props.setSpecialProperties(
- (
- PropertyName.fromElement(caldavxml.CalendarDescription),
- PropertyName.fromElement(caldavxml.CalendarTimeZone),
- ),
- (
- PropertyName.fromElement(customxml.GETCTag),
- PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
- PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
- ),
- )
-
-
-
-class CalendarObject(CommonObjectResource):
- """
- @ivar _path: The path of the .ics file on disk
-
- @type _path: L{FilePath}
- """
- implements(ICalendarObject)
-
- def __init__(self, name, calendar):
- super(CalendarObject, self).__init__(name, calendar)
- self._attachments = {}
-
-
- @property
- def _calendar(self):
- return self._parentCollection
-
-
- def calendar(self):
- return self._calendar
-
-
- @writeOperation
- def setComponent(self, component):
- validateCalendarComponent(self, self._calendar, component)
-
- self._calendar.retrieveOldIndex().addResource(
- self.name(), component
- )
-
- self._component = component
- # FIXME: needs to clear text cache
-
- def do():
- # Mark all properties as dirty, so they can be added back
- # to the newly updated file.
- self.properties().update(self.properties())
-
- backup = None
- if self._path.exists():
- backup = hidden(self._path.temporarySibling())
- self._path.moveTo(backup)
- fh = self._path.open("w")
- try:
- # FIXME: concurrency problem; if this write is interrupted
- # halfway through, the underlying file will be corrupt.
- fh.write(str(component))
- finally:
- fh.close()
-
- # Now re-write the original properties on the updated file
- self.properties().flush()
-
- def undo():
- if backup:
- backup.moveTo(self._path)
- else:
- self._path.remove()
- return undo
- self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
- if self._calendar._notifier:
- self._transaction.postCommit(self._calendar._notifier.notify)
-
- def component(self):
- if self._component is not None:
- return self._component
- text = self.text()
-
- try:
- component = VComponent.fromString(text)
- except InvalidICalendarDataError, e:
- raise InternalDataStoreError(
- "File corruption detected (%s) in file: %s"
- % (e, self._path.path)
- )
- return component
-
-
- def text(self):
- if self._component is not None:
- return str(self._component)
- try:
- fh = self._path.open()
- except IOError, e:
- if e[0] == ENOENT:
- raise NoSuchObjectResourceError(self)
- else:
- raise
-
- try:
- text = fh.read()
- finally:
- fh.close()
-
- if not (
- text.startswith("BEGIN:VCALENDAR\r\n") or
- text.endswith("\r\nEND:VCALENDAR\r\n")
- ):
- raise InternalDataStoreError(
- "File corruption detected (improper start) in file: %s"
- % (self._path.path,)
- )
- return text
-
- iCalendarText = text
-
- def uid(self):
- if not hasattr(self, "_uid"):
- self._uid = self.component().resourceUID()
- return self._uid
-
- def componentType(self):
- if not hasattr(self, "_componentType"):
- self._componentType = self.component().mainType()
- return self._componentType
-
- def organizer(self):
- return self.component().getOrganizer()
-
-
- def createAttachmentWithName(self, name, contentType):
- """
- Implement L{ICalendarObject.removeAttachmentWithName}.
- """
- # Make a (FIXME: temp, remember rollbacks) file in dropbox-land
- attachment = Attachment(self, name)
- self._attachments[name] = attachment
- return attachment.store(contentType)
-
-
- def removeAttachmentWithName(self, name):
- """
- Implement L{ICalendarObject.removeAttachmentWithName}.
- """
- # FIXME: rollback, tests for rollback
- self._dropboxPath().child(name).remove()
- if name in self._attachments:
- del self._attachments[name]
-
-
- def attachmentWithName(self, name):
- # Attachments can be local or remote, but right now we only care about
- # local. So we're going to base this on the listing of files in the
- # dropbox and not on the calendar data. However, we COULD examine the
- # 'attach' properties.
-
- if name in self._attachments:
- return self._attachments[name]
- # FIXME: cache consistently (put it in self._attachments)
- if self._dropboxPath().child(name).exists():
- return Attachment(self, name)
- else:
- # FIXME: test for non-existent attachment.
- return None
-
-
- def attendeesCanManageAttachments(self):
- return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
-
-
- def dropboxID(self):
- return dropboxIDFromCalendarObject(self)
-
-
- def _dropboxPath(self):
- dropboxPath = self._parentCollection._home._path.child(
- "dropbox"
- ).child(self.dropboxID())
- if not dropboxPath.isdir():
- dropboxPath.makedirs()
- return dropboxPath
-
-
- def attachments(self):
- # See comment on attachmentWithName.
- return [Attachment(self, name)
- for name in self._dropboxPath().listdir()]
-
- def initPropertyStore(self, props):
- # Setup peruser special properties
- props.setSpecialProperties(
- (
- ),
- (
- PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
- PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
- PropertyName.fromElement(caldavxml.ScheduleTag),
- PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
- PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
- PropertyName.fromElement(caldavxml.Originator),
- PropertyName.fromElement(caldavxml.Recipient),
- PropertyName.fromElement(customxml.ScheduleChanges),
- ),
- )
-
-
-contentTypeKey = PropertyName.fromElement(GETContentType)
-md5key = PropertyName.fromElement(TwistedGETContentMD5)
-
-class AttachmentStorageTransport(object):
-
- implements(ITransport)
-
- def __init__(self, attachment, contentType):
- """
-
- @param attachment:
- @type attachment:
- """
- self._attachment = attachment
- self._contentType = contentType
- self._file = self._attachment._path.open("w")
-
-
- def write(self, data):
- # FIXME: multiple chunks
- self._file.write(data)
-
-
- def loseConnection(self):
- # FIXME: do anything
- self._file.close()
-
- md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
- props = self._attachment.properties()
- props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
- props[md5key] = TwistedGETContentMD5.fromString(md5)
- props.flush()
-
-
-
-class Attachment(FileMetaDataMixin):
- """
- An L{Attachment} is a container for the data associated with a I{locally-
- stored} calendar attachment. That is to say, there will only be
- L{Attachment} objects present on the I{organizer's} copy of and event, and
- only for C{ATTACH} properties where this server is storing the resource.
- (For example, the organizer may specify an C{ATTACH} property that
- references an URI on a remote server.)
- """
-
- implements(IAttachment)
-
- def __init__(self, calendarObject, name):
- self._calendarObject = calendarObject
- self._name = name
-
-
- def name(self):
- return self._name
-
-
- def properties(self):
- uid = self._calendarObject._parentCollection._home.uid()
- return PropertyStore(uid, lambda :self._path)
-
-
- def store(self, contentType):
- return AttachmentStorageTransport(self, contentType)
-
- def retrieve(self, protocol):
- # FIXME: makeConnection
- # FIXME: actually stream
- # FIMXE: connectionLost
- protocol.dataReceived(self._path.getContent())
- # FIXME: ConnectionDone, not NotImplementedError
- protocol.connectionLost(Failure(NotImplementedError()))
-
- @property
- def _path(self):
- dropboxPath = self._calendarObject._dropboxPath()
- return dropboxPath.child(self.name())
-
-
-
-class CalendarStubResource(CommonStubResource):
- """
- Just enough resource to keep the calendar's sql DB classes going.
- """
-
- def isCalendarCollection(self):
- return True
-
-
- def getChild(self, name):
- calendarObject = self.resource.calendarObjectWithName(name)
- if calendarObject:
- class ChildResource(object):
- def __init__(self, calendarObject):
- self.calendarObject = calendarObject
-
- def iCalendar(self):
- return self.calendarObject.component()
-
- return ChildResource(calendarObject)
- else:
- return None
-
-
-
-class Index(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- def __init__(self, calendar):
- self.calendar = calendar
- stubResource = CalendarStubResource(calendar)
- if self.calendar.name() == 'inbox':
- indexClass = OldInboxIndex
- else:
- indexClass = OldIndex
- self._oldIndex = indexClass(stubResource)
-
-
- def calendarObjects(self):
- calendar = self.calendar
- for name, uid, componentType in self._oldIndex.bruteForceSearch():
- calendarObject = calendar.calendarObjectWithName(name)
-
- # Precache what we found in the index
- calendarObject._uid = uid
- calendarObject._componentType = componentType
-
- yield calendarObject
-
-
-class Invites(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- def __init__(self, calendar):
- self.calendar = calendar
- stubResource = CalendarStubResource(calendar)
- self._oldInvites = InvitesDatabase(stubResource)
Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,542 @@
+# -*- test-case-name: txdav.caldav.datastore.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.
+##
+
+"""
+File calendar store.
+"""
+
+__all__ = [
+ "CalendarStore",
+ "CalendarStoreTransaction",
+ "CalendarHome",
+ "Calendar",
+ "CalendarObject",
+]
+
+import hashlib
+
+from errno import ENOENT
+
+from twisted.internet.interfaces import ITransport
+from twisted.python.failure import Failure
+
+from txdav.base.propertystore.xattr import PropertyStore
+
+from twext.python.vcomponent import InvalidICalendarDataError
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.http_headers import generateContentType
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
+from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
+from twistedcaldav.sharing import InvitesDatabase
+
+from txdav.caldav.icalendarstore import IAttachment
+from txdav.caldav.icalendarstore import ICalendar, ICalendarObject
+from txdav.caldav.icalendarstore import ICalendarHome
+
+from txdav.caldav.datastore.util import (
+ validateCalendarComponent, dropboxIDFromCalendarObject
+)
+
+from txdav.common.datastore.file import (
+ CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
+ CommonObjectResource
+, CommonStubResource)
+
+from txdav.common.icommondatastore import (NoSuchObjectResourceError,
+ InternalDataStoreError)
+from txdav.base.datastore.file import writeOperation, hidden, FileMetaDataMixin
+from txdav.base.propertystore.base import PropertyName
+
+from zope.interface import implements
+
+CalendarStore = CommonDataStore
+
+CalendarStoreTransaction = CommonStoreTransaction
+
+class CalendarHome(CommonHome):
+ implements(ICalendarHome)
+
+ def __init__(self, uid, path, calendarStore, transaction, notifier):
+ super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
+
+ self._childClass = Calendar
+
+
+ def calendarWithName(self, name):
+ if name in ('dropbox', 'notifications', 'freebusy'):
+ # "dropbox" is a file storage area, not a calendar.
+ return None
+ else:
+ return self.childWithName(name)
+
+
+ createCalendarWithName = CommonHome.createChildWithName
+ removeCalendarWithName = CommonHome.removeChildWithName
+
+ def calendars(self):
+ """
+ Return a generator of the child resource objects.
+ """
+ for child in self.children():
+ if child.name() in ('dropbox', 'notification'):
+ continue
+ yield child
+
+ def listCalendars(self):
+ """
+ Return a generator of the child resource names.
+ """
+ for name in self.listChildren():
+ if name in ('dropbox', 'notification'):
+ continue
+ yield name
+
+
+ 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
+
+
+ @property
+ def _calendarStore(self):
+ return self._dataStore
+
+
+ def createdHome(self):
+ self.createCalendarWithName("calendar")
+ defaultCal = self.calendarWithName("calendar")
+ props = defaultCal.properties()
+ props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+ Opaque())
+ self.createCalendarWithName("inbox")
+
+
+
+class Calendar(CommonHomeChild):
+ """
+ File-based implementation of L{ICalendar}.
+ """
+ implements(ICalendar)
+
+ def __init__(self, name, calendarHome, notifier, realName=None):
+ """
+ Initialize a calendar pointing at a path on disk.
+
+ @param name: the subdirectory of calendarHome where this calendar
+ resides.
+ @type name: C{str}
+
+ @param calendarHome: the home containing this calendar.
+ @type calendarHome: L{CalendarHome}
+
+ @param realName: If this calendar was just created, the name which it
+ will eventually have on disk.
+ @type realName: C{str}
+ """
+ super(Calendar, self).__init__(name, calendarHome, notifier,
+ realName=realName)
+
+ self._index = Index(self)
+ self._invites = Invites(self)
+ self._objectResourceClass = CalendarObject
+
+
+ @property
+ def _calendarHome(self):
+ return self._home
+
+
+ def resourceType(self):
+ return ResourceType.calendar #@UndefinedVariable
+
+
+ ownerCalendarHome = CommonHomeChild.ownerHome
+ calendarObjects = CommonHomeChild.objectResources
+ listCalendarObjects = CommonHomeChild.listObjectResources
+ calendarObjectWithName = CommonHomeChild.objectResourceWithName
+ calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
+ createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
+ removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
+ removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+ calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+ def calendarObjectsInTimeRange(self, start, end, timeZone):
+ raise NotImplementedError()
+
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ PropertyName.fromElement(caldavxml.CalendarDescription),
+ PropertyName.fromElement(caldavxml.CalendarTimeZone),
+ ),
+ (
+ PropertyName.fromElement(customxml.GETCTag),
+ PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
+ PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
+ ),
+ )
+
+
+
+class CalendarObject(CommonObjectResource):
+ """
+ @ivar _path: The path of the .ics file on disk
+
+ @type _path: L{FilePath}
+ """
+ implements(ICalendarObject)
+
+ def __init__(self, name, calendar):
+ super(CalendarObject, self).__init__(name, calendar)
+ self._attachments = {}
+
+
+ @property
+ def _calendar(self):
+ return self._parentCollection
+
+
+ def calendar(self):
+ return self._calendar
+
+
+ @writeOperation
+ def setComponent(self, component, inserting=False):
+ validateCalendarComponent(self, self._calendar, component, inserting)
+
+ self._calendar.retrieveOldIndex().addResource(
+ self.name(), component
+ )
+
+ self._component = component
+ # FIXME: needs to clear text cache
+
+ def do():
+ # Mark all properties as dirty, so they can be added back
+ # to the newly updated file.
+ self.properties().update(self.properties())
+
+ backup = None
+ if self._path.exists():
+ backup = hidden(self._path.temporarySibling())
+ self._path.moveTo(backup)
+ fh = self._path.open("w")
+ try:
+ # FIXME: concurrency problem; if this write is interrupted
+ # halfway through, the underlying file will be corrupt.
+ fh.write(str(component))
+ finally:
+ fh.close()
+
+ # Now re-write the original properties on the updated file
+ self.properties().flush()
+
+ def undo():
+ if backup:
+ backup.moveTo(self._path)
+ else:
+ self._path.remove()
+ return undo
+ self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
+ if self._calendar._notifier:
+ self._transaction.postCommit(self._calendar._notifier.notify)
+
+ def component(self):
+ if self._component is not None:
+ return self._component
+ text = self.text()
+
+ try:
+ component = VComponent.fromString(text)
+ except InvalidICalendarDataError, e:
+ raise InternalDataStoreError(
+ "File corruption detected (%s) in file: %s"
+ % (e, self._path.path)
+ )
+ return component
+
+
+ def text(self):
+ if self._component is not None:
+ return str(self._component)
+ try:
+ fh = self._path.open()
+ except IOError, e:
+ if e[0] == ENOENT:
+ raise NoSuchObjectResourceError(self)
+ else:
+ raise
+
+ try:
+ text = fh.read()
+ finally:
+ fh.close()
+
+ if not (
+ text.startswith("BEGIN:VCALENDAR\r\n") or
+ text.endswith("\r\nEND:VCALENDAR\r\n")
+ ):
+ raise InternalDataStoreError(
+ "File corruption detected (improper start) in file: %s"
+ % (self._path.path,)
+ )
+ return text
+
+ iCalendarText = text
+
+ def uid(self):
+ if not hasattr(self, "_uid"):
+ self._uid = self.component().resourceUID()
+ return self._uid
+
+ def componentType(self):
+ if not hasattr(self, "_componentType"):
+ self._componentType = self.component().mainType()
+ return self._componentType
+
+ def organizer(self):
+ return self.component().getOrganizer()
+
+
+ def createAttachmentWithName(self, name, contentType):
+ """
+ Implement L{ICalendarObject.removeAttachmentWithName}.
+ """
+ # Make a (FIXME: temp, remember rollbacks) file in dropbox-land
+ attachment = Attachment(self, name)
+ self._attachments[name] = attachment
+ return attachment.store(contentType)
+
+
+ def removeAttachmentWithName(self, name):
+ """
+ Implement L{ICalendarObject.removeAttachmentWithName}.
+ """
+ # FIXME: rollback, tests for rollback
+ self._dropboxPath().child(name).remove()
+ if name in self._attachments:
+ del self._attachments[name]
+
+
+ def attachmentWithName(self, name):
+ # Attachments can be local or remote, but right now we only care about
+ # local. So we're going to base this on the listing of files in the
+ # dropbox and not on the calendar data. However, we COULD examine the
+ # 'attach' properties.
+
+ if name in self._attachments:
+ return self._attachments[name]
+ # FIXME: cache consistently (put it in self._attachments)
+ if self._dropboxPath().child(name).exists():
+ return Attachment(self, name)
+ else:
+ # FIXME: test for non-existent attachment.
+ return None
+
+
+ def attendeesCanManageAttachments(self):
+ return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+
+
+ def dropboxID(self):
+ return dropboxIDFromCalendarObject(self)
+
+
+ def _dropboxPath(self):
+ dropboxPath = self._parentCollection._home._path.child(
+ "dropbox"
+ ).child(self.dropboxID())
+ if not dropboxPath.isdir():
+ dropboxPath.makedirs()
+ return dropboxPath
+
+
+ def attachments(self):
+ # See comment on attachmentWithName.
+ return [Attachment(self, name)
+ for name in self._dropboxPath().listdir()]
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ ),
+ (
+ PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
+ PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
+ PropertyName.fromElement(caldavxml.ScheduleTag),
+ PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
+ PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
+ PropertyName.fromElement(caldavxml.Originator),
+ PropertyName.fromElement(caldavxml.Recipient),
+ PropertyName.fromElement(customxml.ScheduleChanges),
+ ),
+ )
+
+
+contentTypeKey = PropertyName.fromElement(GETContentType)
+md5key = PropertyName.fromElement(TwistedGETContentMD5)
+
+class AttachmentStorageTransport(object):
+
+ implements(ITransport)
+
+ def __init__(self, attachment, contentType):
+ """
+
+ @param attachment:
+ @type attachment:
+ """
+ self._attachment = attachment
+ self._contentType = contentType
+ self._file = self._attachment._path.open("w")
+
+
+ def write(self, data):
+ # FIXME: multiple chunks
+ self._file.write(data)
+
+
+ def loseConnection(self):
+ # FIXME: do anything
+ self._file.close()
+
+ md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
+ props = self._attachment.properties()
+ props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
+ props[md5key] = TwistedGETContentMD5.fromString(md5)
+ props.flush()
+
+
+
+class Attachment(FileMetaDataMixin):
+ """
+ An L{Attachment} is a container for the data associated with a I{locally-
+ stored} calendar attachment. That is to say, there will only be
+ L{Attachment} objects present on the I{organizer's} copy of and event, and
+ only for C{ATTACH} properties where this server is storing the resource.
+ (For example, the organizer may specify an C{ATTACH} property that
+ references an URI on a remote server.)
+ """
+
+ implements(IAttachment)
+
+ def __init__(self, calendarObject, name):
+ self._calendarObject = calendarObject
+ self._name = name
+
+
+ def name(self):
+ return self._name
+
+
+ def properties(self):
+ uid = self._calendarObject._parentCollection._home.uid()
+ return PropertyStore(uid, lambda :self._path)
+
+
+ def store(self, contentType):
+ return AttachmentStorageTransport(self, contentType)
+
+ def retrieve(self, protocol):
+ # FIXME: makeConnection
+ # FIXME: actually stream
+ # FIMXE: connectionLost
+ protocol.dataReceived(self._path.getContent())
+ # FIXME: ConnectionDone, not NotImplementedError
+ protocol.connectionLost(Failure(NotImplementedError()))
+
+ @property
+ def _path(self):
+ dropboxPath = self._calendarObject._dropboxPath()
+ return dropboxPath.child(self.name())
+
+
+
+class CalendarStubResource(CommonStubResource):
+ """
+ Just enough resource to keep the calendar's sql DB classes going.
+ """
+
+ def isCalendarCollection(self):
+ return True
+
+
+ def getChild(self, name):
+ calendarObject = self.resource.calendarObjectWithName(name)
+ if calendarObject:
+ class ChildResource(object):
+ def __init__(self, calendarObject):
+ self.calendarObject = calendarObject
+
+ def iCalendar(self):
+ return self.calendarObject.component()
+
+ return ChildResource(calendarObject)
+ else:
+ return None
+
+
+
+class Index(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, calendar):
+ self.calendar = calendar
+ stubResource = CalendarStubResource(calendar)
+ if self.calendar.name() == 'inbox':
+ indexClass = OldInboxIndex
+ else:
+ indexClass = OldIndex
+ self._oldIndex = indexClass(stubResource)
+
+
+ def calendarObjects(self):
+ calendar = self.calendar
+ for name, uid, componentType in self._oldIndex.bruteForceSearch():
+ calendarObject = calendar.calendarObjectWithName(name)
+
+ # Precache what we found in the index
+ calendarObject._uid = uid
+ calendarObject._componentType = componentType
+
+ yield calendarObject
+
+
+class Invites(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, calendar):
+ self.calendar = calendar
+ stubResource = CalendarStubResource(calendar)
+ self._oldInvites = InvitesDatabase(stubResource)
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,3283 +0,0 @@
-# -*- 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__ = [
- "PostgresStore",
- "PostgresCalendarHome",
- "PostgresCalendar",
- "PostgresCalendarObject",
- "PostgresAddressBookHome",
- "PostgresAddressBook",
- "PostgresAddressBookObject",
-]
-
-import datetime
-import StringIO
-
-from twistedcaldav.sharing import SharedCollectionRecord #@UnusedImport
-
-from inspect import getargspec
-from zope.interface.declarations import implements
-
-from twisted.application.service import Service
-from twisted.internet.error import ConnectionLost
-from twisted.internet.interfaces import ITransport
-from twisted.python import hashlib
-from twisted.python.failure import Failure
-from twisted.internet.defer import succeed
-from twisted.python.modules import getModule
-
-from twext.web2.dav.element.rfc2518 import ResourceType
-
-from txdav.idav import IDataStore, AlreadyFinishedError
-from txdav.common.inotifications import (INotificationCollection,
- INotificationObject)
-
-from txdav.common.icommondatastore import (
- ObjectResourceNameAlreadyExistsError, HomeChildNameAlreadyExistsError,
- NoSuchHomeChildError, NoSuchObjectResourceError)
-from txcaldav.calendarstore.util import (validateCalendarComponent,
- validateAddressBookComponent, dropboxIDFromCalendarObject, CalendarSyncTokenHelper,
- AddressbookSyncTokenHelper)
-from txdav.datastore.file import cached
-
-from txcaldav.icalendarstore import (ICalendarTransaction, ICalendarHome,
- ICalendar, ICalendarObject, IAttachment)
-from txcarddav.iaddressbookstore import (IAddressBookTransaction,
- IAddressBookHome, IAddressBook, IAddressBookObject)
-from txdav.propertystore.base import AbstractPropertyStore, PropertyName
-from txdav.propertystore.none import PropertyStore
-
-from twext.web2.http_headers import MimeType, generateContentType
-from twext.web2.dav.element.parser import WebDAVDocument
-
-from twext.python.log import Logger, LoggingMixIn
-from twext.python.vcomponent import VComponent
-
-from twistedcaldav import carddavxml
-from twistedcaldav.config import config
-from twistedcaldav.customxml import NotificationType
-from twistedcaldav.dateops import normalizeForIndex
-from twistedcaldav.index import IndexedSearchException, ReservationError,\
- SyncTokenValidException
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-from twistedcaldav.notifications import NotificationRecord
-from twistedcaldav.query import calendarqueryfilter, calendarquery, \
- addressbookquery
-from twistedcaldav.query.sqlgenerator import sqlgenerator
-from twistedcaldav.sharing import Invite
-from twistedcaldav.vcard import Component as VCard
-
-from vobject.icalendar import utc
-
-v1_schema = getModule(__name__).filePath.sibling(
- "postgres_schema_v1.sql").getContent()
-
-log = Logger()
-
-# FIXME: these constants are in the schema, and should probably be discovered
-# from there somehow.
-
-_BIND_STATUS_INVITED = 0
-_BIND_STATUS_ACCEPTED = 1
-_BIND_STATUS_DECLINED = 2
-_BIND_STATUS_INVALID = 3
-
-_ATTACHMENTS_MODE_WRITE = 1
-
-_BIND_MODE_OWN = 0
-_BIND_MODE_READ = 1
-_BIND_MODE_WRITE = 2
-
-
-#
-# Duration into the future through which recurrences are expanded in the index
-# by default. This is a caching parameter which affects the size of the index;
-# it does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-default_future_expansion_duration = datetime.timedelta(days=365 * 1)
-
-#
-# Maximum duration into the future through which recurrences are expanded in the
-# index. This is a caching parameter which affects the size of the index; it
-# does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-# When a search is performed on a time span that goes beyond that which is
-# expanded in the index, we have to open each resource which may have data in
-# that time period. In order to avoid doing that multiple times, we want to
-# cache those results. However, we don't necessarily want to cache all
-# occurrences into some obscenely far-in-the-future date, so we cap the caching
-# period. Searches beyond this period will always be relatively expensive for
-# resources with occurrences beyond this period.
-#
-maximum_future_expansion_duration = datetime.timedelta(days=365 * 5)
-
-icalfbtype_to_indexfbtype = {
- "UNKNOWN" : 0,
- "FREE" : 1,
- "BUSY" : 2,
- "BUSY-UNAVAILABLE": 3,
- "BUSY-TENTATIVE" : 4,
-}
-
-indexfbtype_to_icalfbtype = {
- 0: '?',
- 1: 'F',
- 2: 'B',
- 3: 'U',
- 4: 'T',
-}
-
-
-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
-
-
-
-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):
- 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):
- self._delitem_uid(key, uid)
- 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):
- self._txn.execSQL(
- "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):
- 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])
-
-
-
-class PostgresCalendarObject(object):
- implements(ICalendarObject)
-
- def __init__(self, calendar, name, resid):
- self._calendar = calendar
- self._name = name
- self._resourceID = resid
- self._calendarText = None
-
-
- @property
- def _txn(self):
- return self._calendar._txn
-
-
- def uid(self):
- return self.component().resourceUID()
-
-
- def organizer(self):
- return self.component().getOrganizer()
-
-
- def dropboxID(self):
- return dropboxIDFromCalendarObject(self)
-
-
- def name(self):
- return self._name
-
-
- def calendar(self):
- return self._calendar
-
-
- def iCalendarText(self):
- if self._calendarText is None:
- text = self._txn.execSQL(
- "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- self._calendarText = text
- return text
- else:
- return self._calendarText
-
-
- def component(self):
- return VComponent.fromString(self.iCalendarText())
-
-
- def componentType(self):
- return self.component().mainType()
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.uid(),
- self._txn,
- self._resourceID
- )
-
-
- def setComponent(self, component):
- validateCalendarComponent(self, self._calendar, component)
-
- self.updateDatabase(component)
- self._calendar._updateRevision(self._name)
-
- if self._calendar._notifier:
- self._calendar._home._txn.postCommit(self._calendar._notifier.notify)
-
- def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
- """
- Update the database tables for the new data being written.
-
- @param component: calendar data to store
- @type component: L{Component}
- """
-
- # Decide how far to expand based on the component
- master = component.masterComponent()
- if master is None or not component.isRecurring() and not component.isRecurringUnbounded():
- # When there is no master we have a set of overridden components - index them all.
- # When there is one instance - index it.
- # When bounded - index all.
- expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- else:
- if expand_until:
- expand = expand_until
- else:
- expand = datetime.date.today() + default_future_expansion_duration
-
- if expand > (datetime.date.today() + maximum_future_expansion_duration):
- raise IndexedSearchException
-
- try:
- instances = component.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
- except InvalidOverriddenInstanceError, e:
- log.err("Invalid instance %s when indexing %s in %s" % (e.rid, self._name, self.resource,))
- raise
-
- componentText = str(component)
- self._calendarText = componentText
- organizer = component.getOrganizer()
- if not organizer:
- organizer = ""
-
- # CALENDAR_OBJECT table update
- if inserting:
- self._resourceID = self._txn.execSQL(
- """
- insert into CALENDAR_OBJECT
- (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
- values
- (%s, %s, %s, %s, %s, %s, %s, %s)
- returning RESOURCE_ID
- """,
- # FIXME: correct ATTACHMENTS_MODE based on X-APPLE-
- # DROPBOX
- [
- self._calendar._resourceID,
- self._name,
- componentText,
- component.resourceUID(),
- component.resourceType(),
- _ATTACHMENTS_MODE_WRITE,
- organizer,
- normalizeForIndex(instances.limit) if instances.limit else None,
- ]
- )[0][0]
- else:
- self._txn.execSQL(
- """
- update CALENDAR_OBJECT set
- (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
- =
- (%s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
- where RESOURCE_ID = %s
- """,
- # should really be filling out more fields: ORGANIZER,
- # ORGANIZER_OBJECT, a correct ATTACHMENTS_MODE based on X-APPLE-
- # DROPBOX
- [
- componentText,
- component.resourceUID(),
- component.resourceType(),
- _ATTACHMENTS_MODE_WRITE,
- organizer,
- normalizeForIndex(instances.limit) if instances.limit else None,
- self._resourceID
- ]
- )
-
- # Need to wipe the existing time-range for this and rebuild
- self._txn.execSQL(
- """
- delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
- """,
- [
- self._resourceID,
- ],
- )
-
-
- # CALENDAR_OBJECT table update
- for key in instances:
- instance = instances[key]
- start = instance.start.replace(tzinfo=utc)
- end = instance.end.replace(tzinfo=utc)
- float = instance.start.tzinfo is None
- transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
- instanceid = self._txn.execSQL(
- """
- insert into TIME_RANGE
- (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
- values
- (%s, %s, %s, %s, %s, %s, %s)
- returning
- INSTANCE_ID
- """,
- [
- self._calendar._resourceID,
- self._resourceID,
- float,
- start,
- end,
- icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
- transp,
- ],
- )[0][0]
- peruserdata = component.perUserTransparency(instance.rid)
- for useruid, transp in peruserdata:
- self._txn.execSQL(
- """
- insert into TRANSPARENCY
- (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
- values
- (%s, %s, %s)
- """,
- [
- instanceid,
- useruid,
- transp,
- ],
- )
-
- # Special - for unbounded recurrence we insert a value for "infinity"
- # that will allow an open-ended time-range to always match it.
- if component.isRecurringUnbounded():
- start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
- float = False
- instanceid = self._txn.execSQL(
- """
- insert into TIME_RANGE
- (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
- values
- (%s, %s, %s, %s, %s, %s, %s)
- returning
- INSTANCE_ID
- """,
- [
- self._calendar._resourceID,
- self._resourceID,
- float,
- start,
- end,
- icalfbtype_to_indexfbtype["UNKNOWN"],
- True,
- ],
- )[0][0]
- peruserdata = component.perUserTransparency(None)
- for useruid, transp in peruserdata:
- self._txn.execSQL(
- """
- insert into TRANSPARENCY
- (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
- values
- (%s, %s, %s)
- """,
- [
- instanceid,
- useruid,
- transp,
- ],
- )
-
- def _attachmentPath(self, name):
- attachmentRoot = self._calendar._home._txn._store.attachmentsPath
- try:
- attachmentRoot.createDirectory()
- except:
- pass
- return attachmentRoot.child(
- "%s-%s-%s-%s.attachment" % (
- self._calendar._home.uid(), self._calendar.name(),
- self.name(), name
- )
- )
-
-
- def createAttachmentWithName(self, name, contentType):
- path = self._attachmentPath(name)
- attachment = PostgresAttachment(self, path)
- self._txn.execSQL("""
- insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
- SIZE, MD5, PATH)
- values (%s, %s, %s, %s, %s)
- """,
- [
- self._resourceID, generateContentType(contentType), 0, "",
- attachment._pathValue()
- ]
- )
- return attachment.store(contentType)
-
-
- def attachments(self):
- rows = self._txn.execSQL("""
- select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
- """, [self._resourceID])
- for row in rows:
- demangledName = _pathToName(row[0])
- yield self.attachmentWithName(demangledName)
-
-
- def attachmentWithName(self, name):
- attachment = PostgresAttachment(self, self._attachmentPath(name))
- if attachment._populate():
- return attachment
- else:
- return None
-
-
- def removeAttachmentWithName(self, name):
- attachment = PostgresAttachment(self, self._attachmentPath(name))
- self._calendar._home._txn.postCommit(attachment._path.remove)
- self._txn.execSQL("""
- delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
- PATH = %s
- """, [self._resourceID, attachment._pathValue()])
-
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Calendar objects is text/calendar.
- """
- return MimeType.fromString("text/calendar; charset=utf-8")
-
-
- def md5(self):
- return None
-
-
- def size(self):
- size = self._txn.execSQL(
- "select character_length(ICALENDAR_TEXT) from CALENDAR_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return size
-
-
- def created(self):
- created = self._txn.execSQL(
- "select extract(EPOCH from CREATED) from CALENDAR_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(created)
-
- def modified(self):
- modified = self._txn.execSQL(
- "select extract(EPOCH from MODIFIED) from CALENDAR_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(modified)
-
-
- def attendeesCanManageAttachments(self):
- return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
-
-
-
-def _pathToName(path):
- return path.rsplit(".", 1)[0].split("-", 3)[-1]
-
-
-
-class PostgresAttachment(object):
-
- implements(IAttachment)
-
- def __init__(self, calendarObject, path):
- self._calendarObject = calendarObject
- self._path = path
-
-
- @property
- def _txn(self):
- return self._calendarObject._txn
-
-
- def _populate(self):
- """
- Execute necessary SQL queries to retrieve attributes.
-
- @return: C{True} if this attachment exists, C{False} otherwise.
- """
- rows = self._txn.execSQL(
- """
- select CONTENT_TYPE, SIZE, MD5, extract(EPOCH from CREATED), extract(EPOCH from MODIFIED) from ATTACHMENT where PATH = %s
- """, [self._pathValue()])
- if not rows:
- return False
- self._contentType = MimeType.fromString(rows[0][0])
- self._size = rows[0][1]
- self._md5 = rows[0][2]
- self._created = int(rows[0][3])
- self._modified = int(rows[0][4])
- 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 self._size
-
-
- def created(self):
- return self._created
-
- def modified(self):
- return self._modified
-
-
- 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()
-
-
- @property
- def _txn(self):
- return self.attachment._txn
-
-
- def write(self, data):
- self.buf += data
- self.hash.update(data)
-
-
- def loseConnection(self):
- self.attachment._path.setContent(self.buf)
- pathValue = self.attachment._pathValue()
- contentTypeString = generateContentType(self.contentType)
- self._txn.execSQL(
- "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
- "WHERE PATH = %s",
- [contentTypeString, len(self.buf), self.hash.hexdigest(), pathValue]
- )
-
-
-
-class PostgresLegacyInvitesEmulator(object):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
-
- def __init__(self, calendar):
- self._calendar = calendar
-
-
- @property
- def _txn(self):
- return self._calendar._txn
-
-
- def create(self):
- "No-op, because the index implicitly always exists in the database."
-
-
- def remove(self):
- "No-op, because the index implicitly always exists in the database."
-
-
- def allRecords(self):
- for row in self._txn.execSQL(
- """
- select
- INVITE.INVITE_UID, INVITE.NAME, INVITE.RECIPIENT_ADDRESS,
- CALENDAR_HOME.OWNER_UID, CALENDAR_BIND.BIND_MODE,
- CALENDAR_BIND.BIND_STATUS, CALENDAR_BIND.MESSAGE
- from
- INVITE, CALENDAR_HOME, CALENDAR_BIND
- where
- INVITE.RESOURCE_ID = %s and
- INVITE.HOME_RESOURCE_ID =
- CALENDAR_HOME.RESOURCE_ID and
- CALENDAR_BIND.CALENDAR_RESOURCE_ID =
- INVITE.RESOURCE_ID and
- CALENDAR_BIND.CALENDAR_HOME_RESOURCE_ID =
- INVITE.HOME_RESOURCE_ID
- order by
- INVITE.NAME asc
- """, [self._calendar._resourceID]):
- [inviteuid, common_name, userid, ownerUID,
- bindMode, bindStatus, summary] = row
- # FIXME: this is really the responsibility of the protocol layer.
- state = {
- _BIND_STATUS_INVITED: "NEEDS-ACTION",
- _BIND_STATUS_ACCEPTED: "ACCEPTED",
- _BIND_STATUS_DECLINED: "DECLINED",
- _BIND_STATUS_INVALID: "INVALID",
- }[bindStatus]
- access = {
- _BIND_MODE_READ: "read-only",
- _BIND_MODE_WRITE: "read-write"
- }[bindMode]
- principalURL = "/principals/__uids__/%s/" % (ownerUID,)
- yield Invite(
- inviteuid, userid, principalURL, common_name,
- access, state, summary
- )
-
-
- def recordForUserID(self, userid):
- for record in self.allRecords():
- if record.userid == userid:
- return record
-
-
- def recordForPrincipalURL(self, principalURL):
- for record in self.allRecords():
- if record.principalURL == principalURL:
- return record
-
-
- def recordForInviteUID(self, inviteUID):
- for record in self.allRecords():
- if record.inviteuid == inviteUID:
- return record
-
-
- def addOrUpdateRecord(self, record):
- bindMode = {'read-only': _BIND_MODE_READ,
- 'read-write': _BIND_MODE_WRITE}[record.access]
- bindStatus = {
- "NEEDS-ACTION": _BIND_STATUS_INVITED,
- "ACCEPTED": _BIND_STATUS_ACCEPTED,
- "DECLINED": _BIND_STATUS_DECLINED,
- "INVALID": _BIND_STATUS_INVALID,
- }[record.state]
- # principalURL is derived from a directory record's principalURL() so
- # it will always contain the UID. The form is '/principals/__uids__/x'
- # (and may contain a trailing slash).
- principalUID = record.principalURL.split("/")[3]
- shareeHome = self._txn.calendarHomeWithUID(principalUID, create=True)
- rows = self._txn.execSQL(
- "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
- [record.userid]
- )
- if rows:
- [[resourceID, homeResourceID]] = rows
- # Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
- self._txn.execSQL("""
- update CALENDAR_BIND set BIND_MODE = %s,
- BIND_STATUS = %s, MESSAGE = %s
- where
- CALENDAR_RESOURCE_ID = %s and
- CALENDAR_HOME_RESOURCE_ID = %s
- """, [bindMode, bindStatus, record.summary,
- resourceID, homeResourceID])
- self._txn.execSQL("""
- update INVITE set NAME = %s, INVITE_UID = %s
- where RECIPIENT_ADDRESS = %s
- """,
- [record.name, record.inviteuid, record.userid]
- )
- else:
- self._txn.execSQL(
- """
- insert into INVITE (
- INVITE_UID, NAME,
- HOME_RESOURCE_ID, RESOURCE_ID,
- RECIPIENT_ADDRESS
- )
- values (%s, %s, %s, %s, %s)
- """,
- [
- record.inviteuid, record.name,
- shareeHome._resourceID, self._calendar._resourceID,
- record.userid
- ])
- self._txn.execSQL(
- """
- insert into CALENDAR_BIND
- (
- CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID,
- CALENDAR_RESOURCE_NAME, BIND_MODE, BIND_STATUS,
- SEEN_BY_OWNER, SEEN_BY_SHAREE, MESSAGE
- )
- values (%s, %s, %s, %s, %s, %s, %s, %s)
- """,
- [
- shareeHome._resourceID,
- self._calendar._resourceID,
- None, # this is NULL because it is not bound yet, let's be
- # explicit about that.
- bindMode,
- bindStatus,
- False,
- False,
- record.summary
- ])
-
-
- def removeRecordForUserID(self, userid):
- rec = self.recordForUserID(userid)
- self.removeRecordForInviteUID(rec.inviteuid)
-
-
- def removeRecordForPrincipalURL(self, principalURL):
- raise NotImplementedError("removeRecordForPrincipalURL")
-
-
- def removeRecordForInviteUID(self, inviteUID):
- rows = self._txn.execSQL("""
- select HOME_RESOURCE_ID, RESOURCE_ID from INVITE where
- INVITE_UID = %s
- """, [inviteUID])
- if rows:
- [[homeID, resourceID]] = rows
- self._txn.execSQL(
- "delete from CALENDAR_BIND where "
- "CALENDAR_HOME_RESOURCE_ID = %s and CALENDAR_RESOURCE_ID = %s",
- [homeID, resourceID])
- self._txn.execSQL("delete from INVITE where INVITE_UID = %s",
- [inviteUID])
-
-
-
-class PostgresLegacySharesEmulator(object):
-
- def __init__(self, home):
- self._home = home
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def create(self):
- pass
-
-
- def remove(self):
- pass
-
-
- def allRecords(self):
- # This should have been a smart join that got all these columns at
- # once, but let's not bother to fix it, since the actual query we
- # _want_ to do (just look for calendar binds in a particular homes) is
- # much simpler anyway; we should just do that.
- shareRows = self._txn.execSQL(
- """
- select CALENDAR_RESOURCE_ID, CALENDAR_RESOURCE_NAME, MESSAGE
- from CALENDAR_BIND
- where CALENDAR_HOME_RESOURCE_ID = %s and
- BIND_MODE != %s and
- CALENDAR_RESOURCE_NAME is not null
- """, [self._home._resourceID, _BIND_MODE_OWN])
- for resourceID, resourceName, summary in shareRows:
- [[shareuid]] = self._txn.execSQL(
- """
- select INVITE_UID
- from INVITE
- where RESOURCE_ID = %s and HOME_RESOURCE_ID = %s
- """, [resourceID, self._home._resourceID])
- sharetype = 'I'
- [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
- """
- select CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME
- from CALENDAR_BIND
- where CALENDAR_RESOURCE_ID = %s and
- BIND_MODE = %s
- """, [resourceID, _BIND_MODE_OWN]
- )
- [[ownerUID]] = self._txn.execSQL(
- "select OWNER_UID from CALENDAR_HOME where RESOURCE_ID = %s",
- [ownerHomeID])
- hosturl = '/calendars/__uids__/%s/%s' % (
- ownerUID, ownerResourceName
- )
- localname = resourceName
- record = SharedCollectionRecord(
- shareuid, sharetype, hosturl, localname, summary
- )
- yield record
-
-
- def _search(self, **kw):
- [[key, value]] = kw.items()
- for record in self.allRecords():
- if getattr(record, key) == value:
- return record
-
- def recordForLocalName(self, localname):
- return self._search(localname=localname)
-
- def recordForShareUID(self, shareUID):
- return self._search(shareuid=shareUID)
-
-
- def addOrUpdateRecord(self, record):
-# print '*** SHARING***: Adding or updating this record:'
-# import pprint
-# pprint.pprint(record.__dict__)
- # record.hosturl -> /calendars/__uids__/<uid>/<calendarname>
- splithost = record.hosturl.split('/')
- ownerUID = splithost[3]
- ownerCalendarName = splithost[4]
- ownerHome = self._txn.calendarHomeWithUID(ownerUID)
- ownerCalendar = ownerHome.calendarWithName(ownerCalendarName)
- calendarResourceID = ownerCalendar._resourceID
-
- # There needs to be a bind already, one that corresponds to the
- # invitation. The invitation's UID is the same as the share UID. I
- # just need to update its 'localname', i.e.
- # CALENDAR_BIND.CALENDAR_RESOURCE_NAME.
-
- self._txn.execSQL(
- """
- update CALENDAR_BIND set CALENDAR_RESOURCE_NAME = %s
- where CALENDAR_HOME_RESOURCE_ID = %s and CALENDAR_RESOURCE_ID = %s
- """,
- [record.localname, self._home._resourceID, calendarResourceID]
- )
-
-
- def removeRecordForLocalName(self, localname):
- self._txn.execSQL(
- "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s "
- "and CALENDAR_HOME_RESOURCE_ID = %s",
- [localname, self._home._resourceID]
- )
-
-
- def removeRecordForShareUID(self, shareUID):
- pass
-# c = self._home._cursor()
-# c.execute(
-# "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s "
-# "and CALENDAR_HOME_RESOURCE_ID = %s",
-# [self._home._resourceID]
-# )
-
-
-
-class postgresqlgenerator(sqlgenerator):
- """
- Query generator for postgreSQL indexed searches. (Currently unused: work
- in progress.)
- """
-
- ISOP = " = "
- CONTAINSOP = " LIKE "
- NOTCONTAINSOP = " NOT LIKE "
- FIELDS = {
- "TYPE": "CALENDAR_OBJECT.ICALENDAR_TYPE",
- "UID": "CALENDAR_OBJECT.ICALENDAR_UID",
- }
-
- def __init__(self, expr, calendarid, userid):
- self.RESOURCEDB = "CALENDAR_OBJECT"
- self.TIMESPANDB = "TIME_RANGE"
- self.TIMESPANTEST = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE < %s AND TIME_RANGE.END_DATE > %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE < %s AND TIME_RANGE.END_DATE > %s))"
- self.TIMESPANTEST_NOEND = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.END_DATE > %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.END_DATE > %s))"
- self.TIMESPANTEST_NOSTART = "((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE < %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE < %s))"
- self.TIMESPANTEST_TAIL_PIECE = " AND TIME_RANGE.CALENDAR_OBJECT_RESOURCE_ID = CALENDAR_OBJECT.RESOURCE_ID AND CALENDAR_OBJECT.CALENDAR_RESOURCE_ID = %s"
- self.TIMESPANTEST_JOIN_ON_PIECE = "TIME_RANGE.INSTANCE_ID = TRANSPARENCY.TIME_RANGE_INSTANCE_ID AND TRANSPARENCY.USER_ID = %s"
-
- super(postgresqlgenerator, self).__init__(expr, calendarid, userid)
-
-
- def generate(self):
- """
- Generate the actual SQL 'where ...' expression from the passed in
- expression tree.
-
- @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
- partial SQL statement, and the C{list} is the list of argument
- substitutions to use with the SQL API execute method.
- """
-
- # Init state
- self.sout = StringIO.StringIO()
- self.arguments = []
- self.substitutions = []
- self.usedtimespan = False
-
- # Generate ' where ...' partial statement
- self.sout.write(self.WHERE)
- self.generateExpression(self.expression)
-
- # Prefix with ' from ...' partial statement
- select = self.FROM + self.RESOURCEDB
- if self.usedtimespan:
- self.frontArgument(self.userid)
- select += ", %s LEFT OUTER JOIN %s ON (%s)" % (
- self.TIMESPANDB,
- self.TRANSPARENCYDB,
- self.TIMESPANTEST_JOIN_ON_PIECE
- )
- select += self.sout.getvalue()
-
- select = select % tuple(self.substitutions)
-
- return select, self.arguments
-
-
- def addArgument(self, arg):
- self.arguments.append(arg)
- self.substitutions.append("%s")
- self.sout.write("%s")
-
- def setArgument(self, arg):
- self.arguments.append(arg)
- self.substitutions.append("%s")
-
- def frontArgument(self, arg):
- self.arguments.insert(0, arg)
- self.substitutions.insert(0, "%s")
-
- def containsArgument(self, arg):
- return "%%%s%%" % (arg,)
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
- def __init__(self, index, cachePool=None):
- self.index = index
- self._cachePool = cachePool
-
- def _key(self, uid):
- return 'reservation:%s' % (
- hashlib.md5('%s:%s' % (uid,
- self.index.resource._resourceID)).hexdigest())
-
- def reserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Reserving UID %r @ %r" % (
- uid,
- self.index.resource))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s already reserved for calendar collection %s."
- % (uid, self.index.resource._name)
- )
-
- d = self.getCachePool().add(self._key(uid),
- 'reserved',
- expireTime=config.UIDReservationTimeOut)
- d.addCallback(_handleFalse)
- return d
-
-
- def unreserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Unreserving UID %r @ %r" % (
- uid,
- self.index.resource))
-
- def _handleFalse(result):
- if result is False:
- raise ReservationError(
- "UID %s is not reserved for calendar collection %s."
- % (uid, self.index.resource._resourceID)
- )
-
- d = self.getCachePool().delete(self._key(uid))
- d.addCallback(_handleFalse)
- return d
-
-
- def isReservedUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Is reserved UID %r @ %r" % (
- uid,
- self.index.resource))
-
- def _checkValue((flags, value)):
- if value is None:
- return False
- else:
- return True
-
- d = self.getCachePool().get(self._key(uid))
- d.addCallback(_checkValue)
- return d
-
-class DummyUIDReserver(LoggingMixIn):
-
- def __init__(self, index):
- self.index = index
- self.reservations = set()
-
- def _key(self, uid):
- return 'reservation:%s' % (
- hashlib.md5('%s:%s' % (uid,
- self.index.resource._resourceID)).hexdigest())
-
- def reserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Reserving UID %r @ %r" % (
- uid,
- self.index.resource))
-
- key = self._key(uid)
- if key in self.reservations:
- raise ReservationError(
- "UID %s already reserved for calendar collection %s."
- % (uid, self.index.resource._name)
- )
- self.reservations.add(key)
- return succeed(None)
-
-
- def unreserveUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Unreserving UID %r @ %r" % (
- uid,
- self.index.resource))
-
- key = self._key(uid)
- if key in self.reservations:
- self.reservations.remove(key)
- return succeed(None)
-
-
- def isReservedUID(self, uid):
- uid = uid.encode('utf-8')
- self.log_debug("Is reserved UID %r @ %r" % (
- uid,
- self.index.resource))
- key = self._key(uid)
- return succeed(key in self.reservations)
-
-class PostgresLegacyIndexEmulator(LoggingMixIn):
- """
- Emulator for L{twistedcaldv.index.Index} and
- L{twistedcaldv.index.IndexSchedule}.
- """
-
- def __init__(self, calendar):
- self.resource = self.calendar = calendar
- if (
- hasattr(config, "Memcached") and
- config.Memcached.Pools.Default.ClientEnabled
- ):
- self.reserver = MemcachedUIDReserver(self)
- else:
- # This is only used with unit tests
- self.reserver = DummyUIDReserver(self)
-
- @property
- def _txn(self):
- return self.calendar._txn
-
-
- def reserveUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(None)
- else:
- return self.reserver.reserveUID(uid)
-
-
- def unreserveUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(None)
- else:
- return self.reserver.unreserveUID(uid)
-
-
- def isReservedUID(self, uid):
- if self.calendar._name == "inbox":
- return succeed(False)
- else:
- return self.reserver.isReservedUID(uid)
-
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation which would add the
- specified UID to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- if self.calendar._name == "inbox":
- return True
- else:
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
-
- def resourceUIDForName(self, name):
- obj = self.calendar.calendarObjectWithName(name)
- if obj is None:
- return None
- return obj.uid()
-
-
- def resourceNameForUID(self, uid):
- obj = self.calendar.calendarObjectWithUID(uid)
- if obj is None:
- return None
- return obj.name()
-
-
- def notExpandedBeyond(self, minDate):
- """
- Gives all resources which have not been expanded beyond a given date
- in the database. (Unused; see above L{postgresqlgenerator}.
- """
- return [row[0] for row in self._txn.execSQL(
- "select RESOURCE_NAME from CALENDAR_OBJECT "
- "where RECURRANCE_MAX < %s and CALENDAR_RESOURCE_ID = %s",
- [normalizeForIndex(minDate), self.calendar._resourceID]
- )]
-
-
- def reExpandResource(self, name, expand_until):
- """
- Given a resource name, remove it from the database and re-add it
- with a longer expansion.
- """
- obj = self.calendar.calendarObjectWithName(name)
- obj.updateDatabase(obj.component(), expand_until=expand_until, reCreate=True)
-
- def testAndUpdateIndex(self, minDate):
- # Find out if the index is expanded far enough
- names = self.notExpandedBeyond(minDate)
-
- # Actually expand recurrence max
- for name in names:
- self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
- self.reExpandResource(name, minDate)
-
- def whatchanged(self, revision):
-
- results = [
- (name.encode("utf-8"), deleted)
- for name, deleted in
- self._txn.execSQL(
- """select RESOURCE_NAME, DELETED from CALENDAR_OBJECT_REVISIONS
- where REVISION > %s and CALENDAR_RESOURCE_ID = %s""",
- [revision, self.calendar._resourceID],
- )
- ]
- results.sort(key=lambda x:x[1])
-
- changed = []
- deleted = []
- for name, wasdeleted in results:
- if name:
- if wasdeleted:
- if revision:
- deleted.append(name)
- else:
- changed.append(name)
- else:
- raise SyncTokenValidException
-
- return changed, deleted,
-
- def indexedSearch(self, filter, useruid='', fbtype=False):
- """
- Finds resources matching the given qualifiers.
- @param filter: the L{Filter} for the calendar-query to execute.
- @return: an iterable of tuples for each resource matching the
- given C{qualifiers}. The tuples are C{(name, uid, type)}, where
- C{name} is the resource name, C{uid} is the resource UID, and
- C{type} is the resource iCalendar component type.x
- """
-
- # Make sure we have a proper Filter element and get the partial SQL
- # statement to use.
- if isinstance(filter, calendarqueryfilter.Filter):
- qualifiers = calendarquery.sqlcalendarquery(filter, self.calendar._resourceID, useruid, generator=postgresqlgenerator)
- if qualifiers is not None:
- # Determine how far we need to extend the current expansion of
- # events. If we have an open-ended time-range we will expand one
- # year past the start. That should catch bounded recurrences - unbounded
- # will have been indexed with an "infinite" value always included.
- maxDate, isStartDate = filter.getmaxtimerange()
- if maxDate:
- maxDate = maxDate.date()
- if isStartDate:
- maxDate += datetime.timedelta(days=365)
- self.testAndUpdateIndex(maxDate)
- else:
- # We cannot handler this filter in an indexed search
- raise IndexedSearchException()
-
- else:
- qualifiers = None
-
- # Perform the search
- if qualifiers is None:
- rowiter = self._txn.execSQL(
- "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
- [self.calendar._resourceID, ],
- )
- else:
- if fbtype:
- # For a free-busy time-range query we return all instances
- rowiter = self._txn.execSQL(
- """select DISTINCT
- CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE, CALENDAR_OBJECT.ORGANIZER,
- TIME_RANGE.FLOATING, TIME_RANGE.START_DATE, TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT""" +
- qualifiers[0],
- qualifiers[1]
- )
- else:
- rowiter = self._txn.execSQL(
- "select DISTINCT CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE" +
- qualifiers[0],
- qualifiers[1]
- )
-
- # Check result for missing resources
-
- for row in rowiter:
- if fbtype:
- row = list(row)
- row[4] = 'Y' if row[4] else 'N'
- row[7] = indexfbtype_to_icalfbtype[row[7]]
- row[8] = 'T' if row[9] else 'F'
- del row[9]
- yield row
-
-
- def bruteForceSearch(self):
- return self._txn.execSQL(
- "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from "
- "CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
- [self.calendar._resourceID]
- )
-
-
- def resourcesExist(self, names):
- return list(set(names).intersection(
- set(self.calendar.listCalendarObjects())))
-
-
- def resourceExists(self, name):
- return bool(
- self._txn.execSQL(
- "select RESOURCE_NAME from CALENDAR_OBJECT where "
- "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
- [name, self.calendar._resourceID]
- )
- )
-
-
-
-class PostgresCalendar(CalendarSyncTokenHelper):
-
- implements(ICalendar)
-
- def __init__(self, home, name, resourceID, notifier):
- self._home = home
- self._name = name
- self._resourceID = resourceID
- self._objects = {}
- self._notifier = notifier
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def setSharingUID(self, uid):
- self.properties()._setPerUserUID(uid)
-
-
- def retrieveOldInvites(self):
- return PostgresLegacyInvitesEmulator(self)
-
- def retrieveOldIndex(self):
- return PostgresLegacyIndexEmulator(self)
-
-
- def notifierID(self, label="default"):
- if self._notifier:
- return self._notifier.getID(label)
- else:
- return None
-
-
- def name(self):
- return self._name
-
-
- def rename(self, name):
- oldName = self._name
- self._txn.execSQL(
- "update CALENDAR_BIND set CALENDAR_RESOURCE_NAME = %s "
- "where CALENDAR_RESOURCE_ID = %s AND "
- "CALENDAR_HOME_RESOURCE_ID = %s",
- [name, self._resourceID, self._home._resourceID]
- )
- self._name = name
- # update memos
- del self._home._calendars[oldName]
- self._home._calendars[name] = self
- self._updateSyncToken()
-
-
- def ownerCalendarHome(self):
- return self._home
-
-
- def listCalendarObjects(self):
- # FIXME: see listChildren
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from "
- "CALENDAR_OBJECT where "
- "CALENDAR_RESOURCE_ID = %s",
- [self._resourceID])
- return [row[0] for row in rows]
-
-
- def calendarObjects(self):
- for name in self.listCalendarObjects():
- yield self.calendarObjectWithName(name)
-
-
- @memoized('name', '_objects')
- def calendarObjectWithName(self, name):
- rows = self._txn.execSQL(
- "select RESOURCE_ID from CALENDAR_OBJECT where "
- "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if not rows:
- return None
- resid = rows[0][0]
- return PostgresCalendarObject(self, name, resid)
-
-
- @memoized('uid', '_objects')
- def calendarObjectWithUID(self, uid):
- rows = self._txn.execSQL(
- "select RESOURCE_ID, RESOURCE_NAME from CALENDAR_OBJECT where "
- "ICALENDAR_UID = %s and CALENDAR_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- if not rows:
- return None
- resid = rows[0][0]
- name = rows[0][1]
- return PostgresCalendarObject(self, name, resid)
-
-
- def createCalendarObjectWithName(self, name, component):
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from CALENDAR_OBJECT where "
- " RESOURCE_NAME = %s AND CALENDAR_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if rows:
- raise ObjectResourceNameAlreadyExistsError()
-
- calendarObject = PostgresCalendarObject(self, name, None)
- calendarObject.component = lambda : component
-
- validateCalendarComponent(calendarObject, self, component)
-
- calendarObject.updateDatabase(component, inserting=True)
- self._insertRevision(name)
-
- if self._notifier:
- self._home._txn.postCommit(self._notifier.notify)
-
-
- def removeCalendarObjectWithName(self, name):
- self._txn.execSQL(
- "delete from CALENDAR_OBJECT where RESOURCE_NAME = %s and "
- "CALENDAR_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if self._txn._cursor.rowcount == 0:
- raise NoSuchObjectResourceError()
- self._objects.pop(name, None)
- self._deleteRevision(name)
-
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- def removeCalendarObjectWithUID(self, uid):
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from CALENDAR_OBJECT where "
- "ICALENDAR_UID = %s AND CALENDAR_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- if not rows:
- raise NoSuchObjectResourceError()
- name = rows[0][0]
- self._txn.execSQL(
- "delete from CALENDAR_OBJECT where ICALENDAR_UID = %s and "
- "CALENDAR_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- self._objects.pop(name, None)
- self._objects.pop(uid, None)
- self._deleteRevision(name)
-
- if self._notifier:
- self._home._txn.postCommit(self._notifier.notify)
-
-
- def calendarObjectsInTimeRange(self, start, end, timeZone):
- raise NotImplementedError()
-
-
- def calendarObjectsSinceToken(self, token):
- raise NotImplementedError()
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.ownerCalendarHome().uid(),
- self._txn,
- self._resourceID
- )
-
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Calendar objects is text/calendar.
- """
- return MimeType.fromString("text/calendar; charset=utf-8")
-
-
- def md5(self):
- return None
-
-
- def size(self):
- return 0
-
-
- def created(self):
- created = self._txn.execSQL(
- "select extract(EPOCH from CREATED) from CALENDAR where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(created)
-
- def modified(self):
- modified = self._txn.execSQL(
- "select extract(EPOCH from MODIFIED) from CALENDAR where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(modified)
-
-
-class PostgresCalendarHome(object):
-
- implements(ICalendarHome)
-
- def __init__(self, transaction, ownerUID, resourceID, notifier):
- self._txn = transaction
- self._ownerUID = ownerUID
- self._resourceID = resourceID
- self._calendars = {}
- self._notifier = notifier
-
-
- def retrieveOldShares(self):
- return PostgresLegacySharesEmulator(self)
-
-
- def uid(self):
- """
- Retrieve the unique identifier for this calendar home.
-
- @return: a string.
- """
- return self._ownerUID
-
-
- def name(self):
- """
- Implement L{IDataStoreResource.name} to return the uid.
- """
- return self.uid()
-
-
- def transaction(self):
- return self._txn
-
-
- def listChildren(self):
- """
- Retrieve the names of the children in this calendar home.
-
- @return: an iterable of C{str}s.
- """
- # FIXME: not specified on the interface or exercised by the tests, but
- # required by clients of the implementation!
- rows = self._txn.execSQL(
- "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
- "CALENDAR_HOME_RESOURCE_ID = %s "
- "and BIND_MODE = %s ",
- # Right now, we only show owned calendars.
- [self._resourceID, _BIND_MODE_OWN]
- )
- names = [row[0] for row in rows]
- return names
-
-
- def calendars(self):
- """
- Retrieve calendars contained in this calendar home.
-
- @return: an iterable of L{ICalendar}s.
- """
- names = self.listChildren()
- for name in names:
- yield self.calendarWithName(name)
-
-
- @memoized('name', '_calendars')
- 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.
- """
- data = self._txn.execSQL(
- "select CALENDAR_RESOURCE_ID from CALENDAR_BIND where "
- "CALENDAR_RESOURCE_NAME = %s and CALENDAR_HOME_RESOURCE_ID = %s "
- "and BIND_MODE = %s",
- [name, self._resourceID, _BIND_MODE_OWN]
- )
- if not data:
- return None
- resourceID = data[0][0]
- if self._notifier:
- childID = "%s/%s" % (self.uid(), name)
- notifier = self._notifier.clone(label="collection", id=childID)
- else:
- notifier = None
- return PostgresCalendar(self, name, resourceID, notifier)
-
-
- 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):
- rows = self._txn.execSQL(
- "select CALENDAR_RESOURCE_NAME from CALENDAR_BIND where "
- "CALENDAR_RESOURCE_NAME = %s AND "
- "CALENDAR_HOME_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if rows:
- raise HomeChildNameAlreadyExistsError()
- rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
- resourceID = rows[0][0]
- self._txn.execSQL(
- "insert into CALENDAR (RESOURCE_ID) values "
- "(%s)",
- [resourceID])
-
- self._txn.execSQL("""
- insert into CALENDAR_BIND (
- CALENDAR_HOME_RESOURCE_ID,
- 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
- newCalendar = self.calendarWithName(name)
- newCalendar.properties()[
- PropertyName.fromElement(ResourceType)] = calendarType
- newCalendar._updateSyncToken()
-
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- def removeCalendarWithName(self, name):
- self._txn.execSQL(
- "delete from CALENDAR_BIND where CALENDAR_RESOURCE_NAME = %s and "
- "CALENDAR_HOME_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- self._calendars.pop(name, None)
- if self._txn._cursor.rowcount == 0:
- raise NoSuchHomeChildError()
- # FIXME: the schema should probably cascade the calendar delete when
- # the last bind is deleted.
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.uid(),
- self._txn,
- self._resourceID
- )
-
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Calendar objects is text/calendar.
- """
- return None
-
-
- def md5(self):
- return None
-
-
- def size(self):
- return 0
-
-
- def created(self):
- return None
-
-
- def modified(self):
- return None
-
-
- def notifierID(self, label="default"):
- if self._notifier:
- return self._notifier.getID(label)
- else:
- return None
-
-
-
-class PostgresNotificationObject(object):
- implements(INotificationObject)
-
- def __init__(self, home, resourceID):
- self._home = home
- self._resourceID = resourceID
-
-
- def notificationCollection(self):
- return self._home
-
-
- def name(self):
- return self.uid() + ".xml"
-
-
- def contentType(self):
- """
- The content type of NotificationObjects is text/xml.
- """
- return MimeType.fromString("text/xml")
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def setData(self, uid, xmltype, xmldata):
- self.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
- return self._txn.execSQL(
- """
- update NOTIFICATION set NOTIFICATION_UID = %s, XML_TYPE = %s,
- XML_DATA = %s where RESOURCE_ID = %s
- """,
- [uid, xmltype, xmldata, self._resourceID]
- )
-
-
- def _fieldQuery(self, field):
- [[data]] = self._txn.execSQL(
- "select " + field + " from NOTIFICATION where "
- "RESOURCE_ID = %s",
- [self._resourceID])
- return data
-
-
- def xmldata(self):
- return self._fieldQuery("XML_DATA")
-
-
- def uid(self):
- return self._fieldQuery("NOTIFICATION_UID")
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self._home.uid(),
- self._txn,
- self._resourceID
- )
-
-
- def md5(self):
- return hashlib.md5(self.xmldata()).hexdigest()
-
-
- def modified(self):
- modified = self._txn.execSQL(
- "select extract(EPOCH from MODIFIED) from NOTIFICATION where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(modified)
-
-
- def created(self):
- return None
-
-
- def size(self):
- return len(self.xmldata())
-
-
-
-class PostgresLegacyNotificationsEmulator(object):
- def __init__(self, notificationsCollection):
- self._collection = notificationsCollection
-
-
- def _recordForObject(self, notificationObject):
- return NotificationRecord(
- notificationObject.uid(),
- notificationObject.name(),
- notificationObject._fieldQuery("XML_TYPE"))
-
-
- def recordForName(self, name):
- return self._recordForObject(
- self._collection.notificationObjectWithName(name)
- )
-
-
- def recordForUID(self, uid):
- return self._recordForObject(
- self._collection.notificationObjectWithUID(uid)
- )
-
-
- def removeRecordForUID(self, uid):
- self._collection.removeNotificationObjectWithUID(uid)
-
-
- def removeRecordForName(self, name):
- self._collection.removeNotificationObjectWithName(name)
-
-
-
-class PostgresNotificationCollection(object):
-
- implements(INotificationCollection)
-
- def __init__(self, txn, uid, resourceID):
- self._txn = txn
- self._uid = uid
- self._resourceID = resourceID
- self._notifications = {}
-
-
- def retrieveOldIndex(self):
- return PostgresLegacyNotificationsEmulator(self)
-
-
- def name(self):
- return 'notification'
-
-
- def uid(self):
- return self._uid
-
-
- def notificationObjects(self):
- for [uid] in self._txn.execSQL(
- "select (NOTIFICATION_UID) "
- "from NOTIFICATION "
- "where NOTIFICATION_HOME_RESOURCE_ID = %s",
- [self._resourceID]):
- yield self.notificationObjectWithUID(uid)
-
-
- def _nameToUID(self, name):
- """
- Based on the file-backed implementation, the 'name' is just uid +
- ".xml".
- """
- return name.rsplit(".", 1)[0]
-
-
- def notificationObjectWithName(self, name):
- return self.notificationObjectWithUID(self._nameToUID(name))
-
-
- @memoized('uid', '_notifications')
- def notificationObjectWithUID(self, uid):
- rows = self._txn.execSQL(
- "select RESOURCE_ID from NOTIFICATION where NOTIFICATION_UID = %s"
- " and NOTIFICATION_HOME_RESOURCE_ID = %s",
- [uid, self._resourceID])
- if rows:
- [[resourceID]] = rows
- return PostgresNotificationObject(self, resourceID)
- else:
- return None
-
-
- def writeNotificationObject(self, uid, xmltype, xmldata):
- xmltypeString = xmltype.toxml()
- existing = self._txn.execSQL("select NOTIFICATION_UID from NOTIFICATION where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
- [self._resourceID, uid])
- if existing:
- self._txn.execSQL(
- "update NOTIFICATION set XML_TYPE = %s, XML_DATA = %s where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
- [xmltypeString, xmldata, self._resourceID, uid])
- else:
- self._txn.execSQL(
- "insert into NOTIFICATION (NOTIFICATION_HOME_RESOURCE_ID, NOTIFICATION_UID, XML_TYPE, XML_DATA) "
- "values (%s, %s, %s, %s)", [self._resourceID, uid, xmltypeString, xmldata])
- notificationObject = self.notificationObjectWithUID(uid)
- notificationObject.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
-
-
- def removeNotificationObjectWithName(self, name):
- self.removeNotificationObjectWithUID(self._nameToUID(name))
-
-
- def removeNotificationObjectWithUID(self, uid):
- self._txn.execSQL(
- "delete from NOTIFICATION where NOTIFICATION_UID = %s and "
- "NOTIFICATION_HOME_RESOURCE_ID = %s",
- [uid, self._resourceID])
-
-
- def syncToken(self):
- return 'dummy-sync-token'
-
-
- def notificationObjectsSinceToken(self, token):
- changed = []
- removed = []
- token = self.syncToken()
- return (changed, removed, token)
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self._uid,
- self._txn,
- self._resourceID
- )
-
-
-
-class PostgresTransaction(object):
- """
- Transaction implementation for postgres database.
- """
- implements(ICalendarTransaction, IAddressBookTransaction)
-
- def __init__(self, store, connection, notifierFactory, label):
- # print 'STARTING', label
- self._store = store
- self._connection = connection
- self._cursor = connection.cursor()
- self._completed = False
- self._calendarHomes = {}
- self._addressbookHomes = {}
- self._notificationHomes = {}
- self._postCommitOperations = []
- self._notifierFactory = notifierFactory
- self._label = label
-
-
- def store(self):
- return self._store
-
-
- def __repr__(self):
- return 'PG-TXN<%s>' % (self._label,)
-
-
- def execSQL(self, sql, args=[]):
- # print 'EXECUTE %s: %s' % (self._label, sql)
- self._cursor.execute(sql, args)
- if self._cursor.description:
- return self._cursor.fetchall()
- else:
- return None
-
-
- def __del__(self):
- if not self._completed:
- self._connection.rollback()
- self._connection.close()
-
-
- @memoized('uid', '_calendarHomes')
- def calendarHomeWithUID(self, uid, create=False):
- data = self.execSQL(
- "select RESOURCE_ID from CALENDAR_HOME where OWNER_UID = %s",
- [uid]
- )
- if not data:
- if not create:
- return None
- self.execSQL(
- "insert into CALENDAR_HOME (OWNER_UID) values (%s)",
- [uid]
- )
- home = self.calendarHomeWithUID(uid)
- home.createCalendarWithName("calendar")
- return home
- resid = data[0][0]
-
- if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(id=uid)
- else:
- notifier = None
-
- return PostgresCalendarHome(self, uid, resid, notifier)
-
-
- @memoized('uid', '_addressbookHomes')
- def addressbookHomeWithUID(self, uid, create=False):
- data = self.execSQL(
- "select RESOURCE_ID from ADDRESSBOOK_HOME where OWNER_UID = %s",
- [uid]
- )
- if not data:
- if not create:
- return None
- self.execSQL(
- "insert into ADDRESSBOOK_HOME (OWNER_UID) values (%s)",
- [uid]
- )
- home = self.addressbookHomeWithUID(uid)
- home.createAddressBookWithName("addressbook")
- return home
- resid = data[0][0]
-
- if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(id=uid)
- else:
- notifier = None
-
- return PostgresAddressBookHome(self, uid, resid, notifier)
-
-
- @memoized('uid', '_notificationHomes')
- def notificationsWithUID(self, uid):
- """
- Implement notificationsWithUID.
- """
- rows = self.execSQL(
- """
- select RESOURCE_ID from NOTIFICATION_HOME where
- OWNER_UID = %s
- """, [uid])
- if rows:
- [[resourceID]] = rows
- else:
- [[resourceID]] = self.execSQL("select nextval('RESOURCE_ID_SEQ')")
- resourceID = str(resourceID)
- self.execSQL(
- "insert into NOTIFICATION_HOME (RESOURCE_ID, OWNER_UID) "
- "values (%s, %s)", [resourceID, uid])
- return PostgresNotificationCollection(self, uid, resourceID)
-
-
- def abort(self):
- if not self._completed:
- # print 'ABORTING', self._label
- self._completed = True
- self._connection.rollback()
- self._connection.close()
- else:
- raise AlreadyFinishedError()
-
-
- def commit(self):
- if not self._completed:
- # print 'COMPLETING', self._label
- self._completed = True
- self._connection.commit()
- self._connection.close()
- for operation in self._postCommitOperations:
- operation()
- else:
- raise AlreadyFinishedError()
-
-
- def postCommit(self, operation):
- """
- Run things after 'commit.'
- """
- self._postCommitOperations.append(operation)
- # FIXME: implement.
-
-# CARDDAV
-
-class PostgresAddressBookObject(object):
-
- implements(IAddressBookObject)
-
- def __init__(self, addressbook, name, resid):
- self._addressbook = addressbook
- self._name = name
- self._resourceID = resid
- self._vCardText = None
-
-
- @property
- def _txn(self):
- return self._addressbook._txn
-
-
- def uid(self):
- return self.component().resourceUID()
-
-
- def name(self):
- return self._name
-
-
- def addressbook(self):
- return self._addressbook
-
-
- def vCardText(self):
- if self._vCardText is None:
- text = self._txn.execSQL(
- "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- self._vCardText = text
- return text
- else:
- return self._vCardText
-
-
- def component(self):
- return VCard.fromString(self.vCardText())
-
-
- def componentType(self):
- return self.component().mainType()
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.uid(),
- self._txn,
- self._resourceID
- )
-
-
- def setComponent(self, component):
- validateAddressBookComponent(self, self._addressbook, component)
-
- self.updateDatabase(component)
- self._addressbook._updateRevision(self._name)
-
- if self._addressbook._notifier:
- self._addressbook._home._txn.postCommit(self._addressbook._notifier.notify)
-
- def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
- """
- Update the database tables for the new data being written.
-
- @param component: calendar data to store
- @type component: L{Component}
- """
-
- componentText = str(component)
- self._vCardText = componentText
-
- # CALENDAR_OBJECT table update
- if inserting:
- self._resourceID = self._txn.execSQL(
- """
- insert into ADDRESSBOOK_OBJECT
- (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
- values
- (%s, %s, %s, %s)
- returning RESOURCE_ID
- """,
- [
- self._addressbook._resourceID,
- self._name,
- componentText,
- component.resourceUID(),
- ]
- )[0][0]
- else:
- self._txn.execSQL(
- """
- update ADDRESSBOOK_OBJECT set
- (VCARD_TEXT, VCARD_UID, MODIFIED)
- =
- (%s, %s, timezone('UTC', CURRENT_TIMESTAMP))
- where RESOURCE_ID = %s
- """,
- [
- componentText,
- component.resourceUID(),
- self._resourceID
- ]
- )
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Addressbook objects is text/x-vcard.
- """
- return MimeType.fromString("text/vcard; charset=utf-8")
-
-
- def md5(self):
- return None
-
-
- def size(self):
- size = self._txn.execSQL(
- "select character_length(VCARD_TEXT) from ADDRESSBOOK_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return size
-
-
- def created(self):
- created = self._txn.execSQL(
- "select extract(EPOCH from CREATED) from ADDRESSBOOK_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(created)
-
-
- def modified(self):
- modified = self._txn.execSQL(
- "select extract(EPOCH from MODIFIED) from ADDRESSBOOK_OBJECT where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(modified)
-
-
-
-class postgresqladbkgenerator(sqlgenerator):
- """
- Query generator for postgreSQL indexed searches. (Currently unused: work
- in progress.)
- """
-
- ISOP = " = "
- CONTAINSOP = " LIKE "
- NOTCONTAINSOP = " NOT LIKE "
- FIELDS = {
- "UID": "ADDRESSBOOK_OBJECT.VCARD_UID",
- }
-
- def __init__(self, expr, addressbookid):
- self.RESOURCEDB = "ADDRESSBOOK_OBJECT"
-
- super(postgresqladbkgenerator, self).__init__(expr, addressbookid)
-
-
- def generate(self):
- """
- Generate the actual SQL 'where ...' expression from the passed in
- expression tree.
-
- @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
- partial SQL statement, and the C{list} is the list of argument
- substitutions to use with the SQL API execute method.
- """
-
- # Init state
- self.sout = StringIO.StringIO()
- self.arguments = []
- self.substitutions = []
-
- # Generate ' where ...' partial statement
- self.sout.write(self.WHERE)
- self.generateExpression(self.expression)
-
- # Prefix with ' from ...' partial statement
- select = self.FROM + self.RESOURCEDB
- select += self.sout.getvalue()
-
- select = select % tuple(self.substitutions)
-
- return select, self.arguments
-
-
- def addArgument(self, arg):
- self.arguments.append(arg)
- self.substitutions.append("%s")
- self.sout.write("%s")
-
- def setArgument(self, arg):
- self.arguments.append(arg)
- self.substitutions.append("%s")
-
- def frontArgument(self, arg):
- self.arguments.insert(0, arg)
- self.substitutions.insert(0, "%s")
-
- def containsArgument(self, arg):
- return "%%%s%%" % (arg,)
-
-
-class PostgresLegacyABIndexEmulator(object):
- """
- Emulator for L{twistedcaldv.index.Index} and
- L{twistedcaldv.index.IndexSchedule}.
- """
-
- def __init__(self, addressbook):
- self.resource = self.addressbook = addressbook
- if (
- hasattr(config, "Memcached") and
- config.Memcached.Pools.Default.ClientEnabled
- ):
- self.reserver = MemcachedUIDReserver(self)
- else:
- # This is only used with unit tests
- self.reserver = DummyUIDReserver(self)
-
-
- @property
- def _txn(self):
- return self.addressbook._txn
-
-
- def reserveUID(self, uid):
- return self.reserver.reserveUID(uid)
-
-
- def unreserveUID(self, uid):
- return self.reserver.unreserveUID(uid)
-
-
- def isReservedUID(self, uid):
- return self.reserver.isReservedUID(uid)
-
-
- def isAllowedUID(self, uid, *names):
- """
- Checks to see whether to allow an operation which would add the
- specified UID to the index. Specifically, the operation may not
- violate the constraint that UIDs must be unique.
- @param uid: the UID to check
- @param names: the names of resources being replaced or deleted by the
- operation; UIDs associated with these resources are not checked.
- @return: True if the UID is not in the index and is not reserved,
- False otherwise.
- """
- rname = self.resourceNameForUID(uid)
- return (rname is None or rname in names)
-
-
- def resourceUIDForName(self, name):
- obj = self.addressbook.addressbookObjectWithName(name)
- if obj is None:
- return None
- return obj.uid()
-
-
- def resourceNameForUID(self, uid):
- obj = self.addressbook.addressbookObjectWithUID(uid)
- if obj is None:
- return None
- return obj.name()
-
-
- def whatchanged(self, revision):
-
- results = [
- (name.encode("utf-8"), deleted)
- for name, deleted in
- self._txn.execSQL(
- """select RESOURCE_NAME, DELETED from ADDRESSBOOK_OBJECT_REVISIONS
- where REVISION > %s and ADDRESSBOOK_RESOURCE_ID = %s""",
- [revision, self.addressbook._resourceID],
- )
- ]
- results.sort(key=lambda x:x[1])
-
- changed = []
- deleted = []
- for name, wasdeleted in results:
- if name:
- if wasdeleted:
- if revision:
- deleted.append(name)
- else:
- changed.append(name)
- else:
- raise SyncTokenValidException
-
- return changed, deleted,
-
- def searchValid(self, filter):
- if isinstance(filter, carddavxml.Filter):
- qualifiers = addressbookquery.sqladdressbookquery(filter)
- else:
- qualifiers = None
-
- return qualifiers is not None
-
- def search(self, filter):
- """
- Finds resources matching the given qualifiers.
- @param filter: the L{Filter} for the addressbook-query to execute.
- @return: an iterable of tuples for each resource matching the
- given C{qualifiers}. The tuples are C{(name, uid, type)}, where
- C{name} is the resource name, C{uid} is the resource UID, and
- C{type} is the resource iCalendar component type.x
- """
-
- # Make sure we have a proper Filter element and get the partial SQL statement to use.
- if isinstance(filter, carddavxml.Filter):
- qualifiers = addressbookquery.sqladdressbookquery(filter, self.addressbook._resourceID, generator=postgresqladbkgenerator)
- else:
- qualifiers = None
- if qualifiers is not None:
- rowiter = self._txn.execSQL(
- "select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID" +
- qualifiers[0],
- qualifiers[1]
- )
- else:
- rowiter = self._txn.execSQL(
- "select RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
- [self.addressbook._resourceID, ],
- )
-
- for row in rowiter:
- yield row
-
- def indexedSearch(self, filter, useruid='', fbtype=False):
- """
- Always raise L{IndexedSearchException}, since these indexes are not
- fully implemented yet.
- """
- raise IndexedSearchException()
-
-
- def bruteForceSearch(self):
- return self._txn.execSQL(
- "select RESOURCE_NAME, VCARD_UID from "
- "ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
- [self.addressbook._resourceID]
- )
-
-
- def resourcesExist(self, names):
- return list(set(names).intersection(
- set(self.addressbook.listAddressbookObjects())))
-
-
- def resourceExists(self, name):
- return bool(
- self._txn.execSQL(
- "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
- "RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
- [name, self.addressbook._resourceID]
- )
- )
-
-
-class PostgresLegacyABInvitesEmulator(object):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
-
- def __init__(self, addressbook):
- self._addressbook = addressbook
-
-
- @property
- def _txn(self):
- return self._addressbook._txn
-
-
- def create(self):
- "No-op, because the index implicitly always exists in the database."
-
-
- def remove(self):
- "No-op, because the index implicitly always exists in the database."
-
-
- def allRecords(self):
- for row in self._txn.execSQL(
- """
- select
- INVITE.INVITE_UID, INVITE.NAME, INVITE.RECIPIENT_ADDRESS,
- ADDRESSBOOK_HOME.OWNER_UID, ADDRESSBOOK_BIND.BIND_MODE,
- ADDRESSBOOK_BIND.BIND_STATUS, ADDRESSBOOK_BIND.MESSAGE
- from
- INVITE, ADDRESSBOOK_HOME, ADDRESSBOOK_BIND
- where
- INVITE.RESOURCE_ID = %s and
- INVITE.HOME_RESOURCE_ID =
- ADDRESSBOOK_HOME.RESOURCE_ID and
- ADDRESSBOOK_BIND.ADDRESSBOOK_RESOURCE_ID =
- INVITE.RESOURCE_ID and
- ADDRESSBOOK_BIND.ADDRESSBOOK_HOME_RESOURCE_ID =
- INVITE.HOME_RESOURCE_ID
- order by
- INVITE.NAME asc
- """, [self._addressbook._resourceID]):
- [inviteuid, common_name, userid, ownerUID,
- bindMode, bindStatus, summary] = row
- # FIXME: this is really the responsibility of the protocol layer.
- state = {
- _BIND_STATUS_INVITED: "NEEDS-ACTION",
- _BIND_STATUS_ACCEPTED: "ACCEPTED",
- _BIND_STATUS_DECLINED: "DECLINED",
- _BIND_STATUS_INVALID: "INVALID",
- }[bindStatus]
- access = {
- _BIND_MODE_READ: "read-only",
- _BIND_MODE_WRITE: "read-write"
- }[bindMode]
- principalURL = "/principals/__uids__/%s/" % (ownerUID,)
- yield Invite(
- inviteuid, userid, principalURL, common_name,
- access, state, summary
- )
-
-
- def recordForUserID(self, userid):
- for record in self.allRecords():
- if record.userid == userid:
- return record
-
-
- def recordForPrincipalURL(self, principalURL):
- for record in self.allRecords():
- if record.principalURL == principalURL:
- return record
-
-
- def recordForInviteUID(self, inviteUID):
- for record in self.allRecords():
- if record.inviteuid == inviteUID:
- return record
-
-
- def addOrUpdateRecord(self, record):
- bindMode = {'read-only': _BIND_MODE_READ,
- 'read-write': _BIND_MODE_WRITE}[record.access]
- bindStatus = {
- "NEEDS-ACTION": _BIND_STATUS_INVITED,
- "ACCEPTED": _BIND_STATUS_ACCEPTED,
- "DECLINED": _BIND_STATUS_DECLINED,
- "INVALID": _BIND_STATUS_INVALID,
- }[record.state]
- # principalURL is derived from a directory record's principalURL() so
- # it will always contain the UID. The form is '/principals/__uids__/x'
- # (and may contain a trailing slash).
- principalUID = record.principalURL.split("/")[3]
- shareeHome = self._txn.addressbookHomeWithUID(principalUID, create=True)
- rows = self._txn.execSQL(
- "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
- [record.userid]
- )
- if rows:
- [[resourceID, homeResourceID]] = rows
- # Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
- self._txn.execSQL("""
- update ADDRESSBOOK_BIND set BIND_MODE = %s,
- BIND_STATUS = %s, MESSAGE = %s
- where
- ADDRESSBOOK_RESOURCE_ID = %s and
- ADDRESSBOOK_HOME_RESOURCE_ID = %s
- """, [bindMode, bindStatus, record.summary,
- resourceID, homeResourceID])
- self._txn.execSQL("""
- update INVITE set NAME = %s, INVITE_UID = %s
- where RECIPIENT_ADDRESS = %s
- """,
- [record.name, record.inviteuid, record.userid]
- )
- else:
- self._txn.execSQL(
- """
- insert into INVITE (
- INVITE_UID, NAME,
- HOME_RESOURCE_ID, RESOURCE_ID,
- RECIPIENT_ADDRESS
- )
- values (%s, %s, %s, %s, %s)
- """,
- [
- record.inviteuid, record.name,
- shareeHome._resourceID, self._addressbook._resourceID,
- record.userid
- ])
- self._txn.execSQL(
- """
- insert into ADDRESSBOOK_BIND
- (
- ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID,
- ADDRESSBOOK_RESOURCE_NAME, BIND_MODE, BIND_STATUS,
- SEEN_BY_OWNER, SEEN_BY_SHAREE, MESSAGE
- )
- values (%s, %s, %s, %s, %s, %s, %s, %s)
- """,
- [
- shareeHome._resourceID,
- self._addressbook._resourceID,
- None, # this is NULL because it is not bound yet, let's be
- # explicit about that.
- bindMode,
- bindStatus,
- False,
- False,
- record.summary
- ])
-
-
- def removeRecordForUserID(self, userid):
- rec = self.recordForUserID(userid)
- self.removeRecordForInviteUID(rec.inviteuid)
-
-
- def removeRecordForPrincipalURL(self, principalURL):
- raise NotImplementedError("removeRecordForPrincipalURL")
-
-
- def removeRecordForInviteUID(self, inviteUID):
- rows = self._txn.execSQL("""
- select HOME_RESOURCE_ID, RESOURCE_ID from INVITE where
- INVITE_UID = %s
- """, [inviteUID])
- if rows:
- [[homeID, resourceID]] = rows
- self._txn.execSQL(
- "delete from ADDRESSBOOK_BIND where "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s and ADDRESSBOOK_RESOURCE_ID = %s",
- [homeID, resourceID])
- self._txn.execSQL("delete from INVITE where INVITE_UID = %s",
- [inviteUID])
-
-
-
-class PostgresLegacyABSharesEmulator(object):
-
- def __init__(self, home):
- self._home = home
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def create(self):
- pass
-
-
- def remove(self):
- pass
-
-
- def allRecords(self):
- # This should have been a smart join that got all these columns at
- # once, but let's not bother to fix it, since the actual query we
- # _want_ to do (just look for addressbook binds in a particular homes) is
- # much simpler anyway; we should just do that.
- shareRows = self._txn.execSQL(
- """
- select ADDRESSBOOK_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME, MESSAGE
- from ADDRESSBOOK_BIND
- where ADDRESSBOOK_HOME_RESOURCE_ID = %s and
- BIND_MODE != %s and
- ADDRESSBOOK_RESOURCE_NAME is not null
- """, [self._home._resourceID, _BIND_MODE_OWN])
- for resourceID, resourceName, summary in shareRows:
- [[shareuid]] = self._txn.execSQL(
- """
- select INVITE_UID
- from INVITE
- where RESOURCE_ID = %s and HOME_RESOURCE_ID = %s
- """, [resourceID, self._home._resourceID])
- sharetype = 'I'
- [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
- """
- select ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME
- from ADDRESSBOOK_BIND
- where ADDRESSBOOK_RESOURCE_ID = %s and
- BIND_MODE = %s
- """, [resourceID, _BIND_MODE_OWN]
- )
- [[ownerUID]] = self._txn.execSQL(
- "select OWNER_UID from ADDRESSBOOK_HOME where RESOURCE_ID = %s",
- [ownerHomeID])
- hosturl = '/addressbooks/__uids__/%s/%s' % (
- ownerUID, ownerResourceName
- )
- localname = resourceName
- record = SharedCollectionRecord(
- shareuid, sharetype, hosturl, localname, summary
- )
- yield record
-
-
- def _search(self, **kw):
- [[key, value]] = kw.items()
- for record in self.allRecords():
- if getattr(record, key) == value:
- return record
-
- def recordForLocalName(self, localname):
- return self._search(localname=localname)
-
- def recordForShareUID(self, shareUID):
- return self._search(shareuid=shareUID)
-
-
- def addOrUpdateRecord(self, record):
-# print '*** SHARING***: Adding or updating this record:'
-# import pprint
-# pprint.pprint(record.__dict__)
- # record.hosturl -> /addressbooks/__uids__/<uid>/<addressbookname>
- splithost = record.hosturl.split('/')
- ownerUID = splithost[3]
- ownerAddressBookName = splithost[4]
- ownerHome = self._txn.addressbookHomeWithUID(ownerUID)
- ownerAddressBook = ownerHome.addressbookWithName(ownerAddressBookName)
- addressbookResourceID = ownerAddressBook._resourceID
-
- # There needs to be a bind already, one that corresponds to the
- # invitation. The invitation's UID is the same as the share UID. I
- # just need to update its 'localname', i.e.
- # ADDRESSBOOK_BIND.ADDRESSBOOK_RESOURCE_NAME.
-
- self._txn.execSQL(
- """
- update ADDRESSBOOK_BIND set ADDRESSBOOK_RESOURCE_NAME = %s
- where ADDRESSBOOK_HOME_RESOURCE_ID = %s and ADDRESSBOOK_RESOURCE_ID = %s
- """,
- [record.localname, self._home._resourceID, addressbookResourceID]
- )
-
-
- def removeRecordForLocalName(self, localname):
- self._txn.execSQL(
- "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s "
- "and ADDRESSBOOK_HOME_RESOURCE_ID = %s",
- [localname, self._home._resourceID]
- )
-
-
- def removeRecordForShareUID(self, shareUID):
- pass
-# c = self._home._cursor()
-# c.execute(
-# "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s "
-# "and ADDRESSBOOK_HOME_RESOURCE_ID = %s",
-# [self._home._resourceID]
-# )
-
-
-
-
-class PostgresAddressBook(AddressbookSyncTokenHelper):
-
- implements(IAddressBook)
-
- def __init__(self, home, name, resourceID, notifier):
- self._home = home
- self._name = name
- self._resourceID = resourceID
- self._objects = {}
- self._notifier = notifier
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def setSharingUID(self, uid):
- self.properties()._setPerUserUID(uid)
-
-
- def retrieveOldInvites(self):
- return PostgresLegacyABInvitesEmulator(self)
-
- def retrieveOldIndex(self):
- return PostgresLegacyABIndexEmulator(self)
-
-
- def notifierID(self, label="default"):
- if self._notifier:
- return self._notifier.getID(label)
- else:
- return None
-
-
- def name(self):
- return self._name
-
-
- def rename(self, name):
- oldName = self._name
- self._txn.execSQL(
- "update ADDRESSBOOK_BIND set ADDRESSBOOK_RESOURCE_NAME = %s "
- "where ADDRESSBOOK_RESOURCE_ID = %s AND "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
- [name, self._resourceID, self._home._resourceID]
- )
- self._name = name
- # update memos
- del self._home._addressbooks[oldName]
- self._home._addressbooks[name] = self
- self._updateSyncToken()
-
-
- def ownerAddressBookHome(self):
- return self._home
-
-
- def listAddressbookObjects(self):
- # FIXME: see listChildren
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from "
- "ADDRESSBOOK_OBJECT where "
- "ADDRESSBOOK_RESOURCE_ID = %s",
- [self._resourceID])
- return [row[0] for row in rows]
-
-
- def addressbookObjects(self):
- for name in self.listAddressbookObjects():
- yield self.addressbookObjectWithName(name)
-
-
- @memoized('name', '_objects')
- def addressbookObjectWithName(self, name):
- rows = self._txn.execSQL(
- "select RESOURCE_ID from ADDRESSBOOK_OBJECT where "
- "RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if not rows:
- return None
- resid = rows[0][0]
- return PostgresAddressBookObject(self, name, resid)
-
-
- @memoized('uid', '_objects')
- def addressbookObjectWithUID(self, uid):
- rows = self._txn.execSQL(
- "select RESOURCE_ID, RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
- "VCARD_UID = %s and ADDRESSBOOK_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- if not rows:
- return None
- resid = rows[0][0]
- name = rows[0][1]
- return PostgresAddressBookObject(self, name, resid)
-
-
- def createAddressBookObjectWithName(self, name, component):
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
- " RESOURCE_NAME = %s AND ADDRESSBOOK_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if rows:
- raise ObjectResourceNameAlreadyExistsError()
-
- addressbookObject = PostgresAddressBookObject(self, name, None)
- addressbookObject.component = lambda : component
-
- validateAddressBookComponent(addressbookObject, self, component)
-
- addressbookObject.updateDatabase(component, inserting=True)
- self._insertRevision(name)
-
- if self._notifier:
- self._home._txn.postCommit(self._notifier.notify)
-
-
- def removeAddressBookObjectWithName(self, name):
- self._txn.execSQL(
- "delete from ADDRESSBOOK_OBJECT where RESOURCE_NAME = %s and "
- "ADDRESSBOOK_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if self._txn._cursor.rowcount == 0:
- raise NoSuchObjectResourceError()
- self._objects.pop(name, None)
- self._deleteRevision(name)
-
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- def removeAddressBookObjectWithUID(self, uid):
- rows = self._txn.execSQL(
- "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
- "VCARD_UID = %s AND ADDRESSBOOK_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- if not rows:
- raise NoSuchObjectResourceError()
- name = rows[0][0]
- self._txn.execSQL(
- "delete from ADDRESSBOOK_OBJECT where VCARD_UID = %s and "
- "ADDRESSBOOK_RESOURCE_ID = %s",
- [uid, self._resourceID]
- )
- self._objects.pop(name, None)
- self._objects.pop(uid, None)
- self._deleteRevision(name)
-
- if self._notifier:
- self._home._txn.postCommit(self._notifier.notify)
-
-
- def addressbookObjectsSinceToken(self, token):
- raise NotImplementedError()
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.ownerAddressBookHome().uid(),
- self._txn,
- self._resourceID
- )
-
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Addressbook objects is ???
- """
- return None # FIXME: verify
-
-
- def md5(self):
- return None
-
-
- def size(self):
- return 0
-
-
- def created(self):
- created = self._txn.execSQL(
- "select extract(EPOCH from CREATED) from ADDRESSBOOK where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(created)
-
-
- def modified(self):
- modified = self._txn.execSQL(
- "select extract(EPOCH from MODIFIED) from ADDRESSBOOK where "
- "RESOURCE_ID = %s", [self._resourceID]
- )[0][0]
- return int(modified)
-
-
-
-
-class PostgresAddressBookHome(object):
-
- implements(IAddressBookHome)
-
- def __init__(self, transaction, ownerUID, resourceID, notifier):
- self._txn = transaction
- self._ownerUID = ownerUID
- self._resourceID = resourceID
- self._addressbooks = {}
- self._notifier = notifier
-
-
- def retrieveOldShares(self):
- return PostgresLegacyABSharesEmulator(self)
-
-
- def uid(self):
- """
- Retrieve the unique identifier for this calendar home.
-
- @return: a string.
- """
- return self._ownerUID
-
-
- def name(self):
- """
- Implement L{IDataStoreResource.name} to return the uid.
- """
- return self.uid()
-
-
- def listChildren(self):
- """
- Retrieve the names of the children in this addressbook home.
-
- @return: an iterable of C{str}s.
- """
- # FIXME: not specified on the interface or exercised by the tests, but
- # required by clients of the implementation!
- rows = self._txn.execSQL(
- "select ADDRESSBOOK_RESOURCE_NAME from ADDRESSBOOK_BIND where "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s "
- "AND BIND_STATUS != %s",
- [self._resourceID, _BIND_STATUS_DECLINED]
- )
- names = [row[0] for row in rows]
- return names
-
-
- def addressbooks(self):
- """
- Retrieve addressbooks contained in this addressbook home.
-
- @return: an iterable of L{IAddressBook}s.
- """
- names = self.listChildren()
- for name in names:
- yield self.addressbookWithName(name)
-
-
- @memoized('name', '_addressbooks')
- def addressbookWithName(self, name):
- """
- Retrieve the addressbook with the given C{name} contained in this
- addressbook home.
-
- @param name: a string.
- @return: an L{IAddressBook} or C{None} if no such addressbook
- exists.
- """
- data = self._txn.execSQL(
- "select ADDRESSBOOK_RESOURCE_ID from ADDRESSBOOK_BIND where "
- "ADDRESSBOOK_RESOURCE_NAME = %s and "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if not data:
- return None
- resourceID = data[0][0]
- if self._notifier:
- childID = "%s/%s" % (self.uid(), name)
- notifier = self._notifier.clone(label="collection", id=childID)
- else:
- notifier = None
- return PostgresAddressBook(self, name, resourceID, notifier)
-
-
- def createAddressBookWithName(self, name):
- rows = self._txn.execSQL(
- "select ADDRESSBOOK_RESOURCE_NAME from ADDRESSBOOK_BIND where "
- "ADDRESSBOOK_RESOURCE_NAME = %s AND "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- if rows:
- raise HomeChildNameAlreadyExistsError()
- rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
- resourceID = rows[0][0]
- self._txn.execSQL(
- "insert into ADDRESSBOOK (RESOURCE_ID) values "
- "(%s)",
- [resourceID])
-
- self._txn.execSQL("""
- insert into ADDRESSBOOK_BIND (
- ADDRESSBOOK_HOME_RESOURCE_ID,
- ADDRESSBOOK_RESOURCE_ID, ADDRESSBOOK_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]
- )
-
- addressbookType = ResourceType.addressbook #@UndefinedVariable
- newAddressbook = self.addressbookWithName(name)
- newAddressbook.properties()[
- PropertyName.fromElement(ResourceType)] = addressbookType
- newAddressbook._updateSyncToken()
-
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- def removeAddressBookWithName(self, name):
- self._txn.execSQL(
- "delete from ADDRESSBOOK_BIND where ADDRESSBOOK_RESOURCE_NAME = %s and "
- "ADDRESSBOOK_HOME_RESOURCE_ID = %s",
- [name, self._resourceID]
- )
- self._addressbooks.pop(name, None)
- if self._txn._cursor.rowcount == 0:
- raise NoSuchHomeChildError()
- # FIXME: the schema should probably cascade the addressbook delete when
- # the last bind is deleted.
- if self._notifier:
- self._txn.postCommit(self._notifier.notify)
-
-
- @cached
- def properties(self):
- return PropertyStore(
- self.uid(),
- self._txn,
- self._resourceID
- )
-
-
- # IDataStoreResource
- def contentType(self):
- """
- The content type of Addressbook home objects is ???
- """
- return None # FIXME: verify
-
-
- def md5(self):
- return None
-
-
- def size(self):
- return 0
-
-
- def created(self):
- return None
-
-
- def modified(self):
- return None
-
-
- def notifierID(self, label="default"):
- if self._notifier:
- return self._notifier.getID(label)
- else:
- return None
-
-
-#
-
-
-class PostgresStore(Service, object):
-
- implements(IDataStore)
-
- def __init__(self, connectionFactory, notifierFactory, attachmentsPath):
- self.connectionFactory = connectionFactory
- self.notifierFactory = notifierFactory
- self.attachmentsPath = attachmentsPath
-
-
- def newTransaction(self, label="unlabeled"):
- return PostgresTransaction(
- self,
- self.connectionFactory(),
- self.notifierFactory,
- label
- )
-
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres_schema_v1.sql 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/postgres_schema_v1.sql 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,342 +0,0 @@
------------------
--- Resource ID --
------------------
-
-create sequence RESOURCE_ID_SEQ;
-
-
--------------------
--- Calendar Home --
--------------------
-
-create table CALENDAR_HOME (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- OWNER_UID varchar(255) not null unique
-);
-
-
---------------
--- Calendar --
---------------
-
-create table CALENDAR (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- REVISION integer default 0,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
-------------------------
--- Sharing Invitation --
-------------------------
-
-create table INVITE (
- INVITE_UID varchar(255) not null,
- NAME varchar(255) not null,
- RECIPIENT_ADDRESS varchar(255) not null,
- HOME_RESOURCE_ID integer not null,
- RESOURCE_ID integer not null
-);
-
-
----------------------------
--- Sharing Notifications --
----------------------------
-
-create table NOTIFICATION_HOME (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- OWNER_UID varchar(255) not null unique
-);
-
-
-create table NOTIFICATION (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- NOTIFICATION_HOME_RESOURCE_ID integer not null references NOTIFICATION_HOME,
- NOTIFICATION_UID varchar(255) not null,
- XML_TYPE varchar not null,
- XML_DATA varchar not null,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-
- unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID)
-);
-
-
--------------------
--- Calendar Bind --
--------------------
-
--- Joins CALENDAR_HOME and CALENDAR
-
-create table CALENDAR_BIND (
- CALENDAR_HOME_RESOURCE_ID integer not null references CALENDAR_HOME,
- CALENDAR_RESOURCE_ID integer not null references CALENDAR,
-
- -- An invitation which hasn't been accepted yet will not yet have a resource
- -- name, so this field may be null.
-
- CALENDAR_RESOURCE_NAME varchar(255),
- BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
- BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
- SEEN_BY_OWNER boolean not null,
- SEEN_BY_SHAREE boolean not null,
- MESSAGE text,
-
- primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID),
- unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)
-);
-
--- Enumeration of calendar bind modes
-
-create table CALENDAR_BIND_MODE (
- ID integer primary key,
- DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_BIND_MODE values (0, 'own' );
-insert into CALENDAR_BIND_MODE values (1, 'read' );
-insert into CALENDAR_BIND_MODE values (2, 'write');
-
--- Enumeration of statuses
-
-create table CALENDAR_BIND_STATUS (
- ID integer primary key,
- DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_BIND_STATUS values (0, 'invited' );
-insert into CALENDAR_BIND_STATUS values (1, 'accepted');
-insert into CALENDAR_BIND_STATUS values (2, 'declined');
-insert into CALENDAR_BIND_STATUS values (3, 'invalid');
-
-
----------------------
--- Calendar Object --
----------------------
-
-create table CALENDAR_OBJECT (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- CALENDAR_RESOURCE_ID integer not null references CALENDAR,
- RESOURCE_NAME varchar(255) not null,
- ICALENDAR_TEXT text not null,
- ICALENDAR_UID varchar(255) not null,
- ICALENDAR_TYPE varchar(255) not null,
- ATTACHMENTS_MODE integer not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
- ORGANIZER varchar(255),
- ORGANIZER_OBJECT integer references CALENDAR_OBJECT,
- RECURRANCE_MAX date, -- maximum date that recurrences have been expanded to.
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-
- unique(CALENDAR_RESOURCE_ID, RESOURCE_NAME)
-
- -- since the 'inbox' is a 'calendar resource' for the purpose of storing
- -- calendar objects, this constraint has to be selectively enforced by the
- -- application layer.
-
- -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
-);
-
--- Enumeration of attachment modes
-
-create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
- ID integer primary key,
- DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'read' );
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'write');
-
-
------------------
--- Instance ID --
------------------
-
-create sequence INSTANCE_ID_SEQ;
-
-
-----------------
--- Time Range --
-----------------
-
-create table TIME_RANGE (
- INSTANCE_ID integer primary key default nextval('INSTANCE_ID_SEQ'),
- CALENDAR_RESOURCE_ID integer not null references CALENDAR,
- CALENDAR_OBJECT_RESOURCE_ID integer not null references CALENDAR_OBJECT on delete cascade,
- FLOATING boolean not null,
- START_DATE timestamp not null,
- END_DATE timestamp not null,
- FBTYPE integer not null,
- TRANSPARENT boolean not null
-);
-
--- Enumeration of free/busy types
-
-create table FREE_BUSY_TYPE (
- ID integer primary key,
- DESCRIPTION varchar(16) not null unique
-);
-
-insert into FREE_BUSY_TYPE values (0, 'unknown' );
-insert into FREE_BUSY_TYPE values (1, 'free' );
-insert into FREE_BUSY_TYPE values (2, 'busy' );
-insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
-insert into FREE_BUSY_TYPE values (4, 'busy-tentative' );
-
-
-------------------
--- Transparency --
-------------------
-
-create table TRANSPARENCY (
- TIME_RANGE_INSTANCE_ID integer not null references TIME_RANGE on delete cascade,
- USER_ID varchar(255) not null,
- TRANSPARENT boolean not null
-);
-
-
-----------------
--- Attachment --
-----------------
-
-create table ATTACHMENT (
- CALENDAR_OBJECT_RESOURCE_ID integer not null references CALENDAR_OBJECT on delete cascade,
- CONTENT_TYPE varchar(255) not null,
- SIZE integer not null,
- MD5 char(32) not null,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- PATH varchar(1024) not null unique
-);
-
-
-------------------------------
--- Calendar Object Revision --
-------------------------------
-
-create sequence CALENDAR_OBJECT_REVISION_SEQ;
-
-
--------------------------------
--- Calendar Object Revisions --
--------------------------------
-
-create table CALENDAR_OBJECT_REVISIONS (
- CALENDAR_RESOURCE_ID integer not null references CALENDAR on delete cascade,
- RESOURCE_NAME varchar(255) not null,
- REVISION integer not null,
- DELETED boolean not null,
-
- unique(CALENDAR_RESOURCE_ID, RESOURCE_NAME)
-);
-
-
-------------------
--- iTIP Message --
-------------------
-
-create table ITIP_MESSAGE (
- CALENDAR_RESOURCE_ID integer not null references CALENDAR,
- ICALENDAR_TEXT text not null,
- ICALENDAR_UID varchar(255) not null,
- MD5 char(32) not null,
- CHANGES text not null
-);
-
-
------------------------
--- Resource Property --
------------------------
-
-create table RESOURCE_PROPERTY (
- RESOURCE_ID integer not null, -- foreign key: *.RESOURCE_ID
- NAME varchar(255) not null,
- VALUE text not null, -- FIXME: xml?
- VIEWER_UID varchar(255),
-
- primary key(RESOURCE_ID, NAME, VIEWER_UID)
-);
-
-
-----------------------
--- AddressBook Home --
-----------------------
-
-create table ADDRESSBOOK_HOME (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- OWNER_UID varchar(255) not null unique
-);
-
-
------------------
--- AddressBook --
------------------
-
-create table ADDRESSBOOK (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- REVISION integer default 0,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
-----------------------
--- AddressBook Bind --
-----------------------
-
--- Joins ADDRESSBOOK_HOME and ADDRESSBOOK
-
-create table ADDRESSBOOK_BIND (
- ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
- ADDRESSBOOK_RESOURCE_ID integer not null references ADDRESSBOOK,
-
- -- An invitation which hasn't been accepted yet will not yet have a resource
- -- name, so this field may be null.
-
- ADDRESSBOOK_RESOURCE_NAME varchar(255),
- BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
- BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
- SEEN_BY_OWNER boolean not null,
- SEEN_BY_SHAREE boolean not null,
- MESSAGE text, -- FIXME: xml?
-
- primary key(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID),
- unique(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)
-);
-
-
-create table ADDRESSBOOK_OBJECT (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- ADDRESSBOOK_RESOURCE_ID integer not null references ADDRESSBOOK,
- RESOURCE_NAME varchar(255) not null,
- VCARD_TEXT text not null,
- VCARD_UID varchar(255) not null,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-
- unique(ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME),
- unique(ADDRESSBOOK_RESOURCE_ID, VCARD_UID)
-);
-
-------------------------------
--- AddressBook Object Revision --
-------------------------------
-
-create sequence ADDRESSBOOK_OBJECT_REVISION_SEQ;
-
-
--------------------------------
--- AddressBook Object Revisions --
--------------------------------
-
-create table ADDRESSBOOK_OBJECT_REVISIONS (
- ADDRESSBOOK_RESOURCE_ID integer not null references ADDRESSBOOK on delete cascade,
- RESOURCE_NAME varchar(255) not null,
- REVISION integer not null,
- DELETED boolean not null,
-
- unique(ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME)
-);
-
-
Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/scheduling.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/scheduling.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_scheduling -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_scheduling -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
@@ -15,7 +15,7 @@
# limitations under the License.
##
from zope.interface.declarations import implements
-from txcaldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
+from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
ICalendarTransaction
from txdav.idav import IDataStore
from twisted.python.util import FancyEqMixin
Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,634 @@
+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# 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.
+##
+
+__all__ = [
+ "CalendarHome",
+ "Calendar",
+ "CalendarObject",
+]
+
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.http_headers import MimeType, generateContentType
+
+from twisted.internet.error import ConnectionLost
+from twisted.internet.interfaces import ITransport
+from twisted.python import hashlib
+from twisted.python.failure import Failure
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
+from twistedcaldav.dateops import normalizeForIndex
+from twistedcaldav.index import IndexedSearchException
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+
+from txdav.caldav.datastore.util import validateCalendarComponent,\
+ dropboxIDFromCalendarObject
+from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
+ IAttachment
+
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
+ CommonObjectResource
+from txdav.common.datastore.sql_legacy import \
+ PostgresLegacyIndexEmulator, PostgresLegacyInvitesEmulator,\
+ PostgresLegacySharesEmulator
+from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
+ CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
+ _ATTACHMENTS_MODE_WRITE
+from txdav.base.propertystore.base import PropertyName
+
+from vobject.icalendar import utc
+
+import datetime
+
+from zope.interface.declarations import implements
+
+class CalendarHome(CommonHome):
+
+ implements(ICalendarHome)
+
+ def __init__(self, transaction, ownerUID, resourceID, notifier):
+ super(CalendarHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+
+ self._shares = PostgresLegacySharesEmulator(self)
+ self._childClass = Calendar
+ self._childTable = CALENDAR_TABLE
+ self._bindTable = CALENDAR_BIND_TABLE
+
+ createCalendarWithName = CommonHome.createChildWithName
+ removeCalendarWithName = CommonHome.removeChildWithName
+ calendarWithName = CommonHome.childWithName
+ calendars = CommonHome.children
+ listCalendars = CommonHome.listChildren
+
+ 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 createdHome(self):
+ self.createCalendarWithName("calendar")
+ defaultCal = self.calendarWithName("calendar")
+ props = defaultCal.properties()
+ props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+ Opaque())
+ self.createCalendarWithName("inbox")
+
+class Calendar(CommonHomeChild):
+ """
+ File-based implementation of L{ICalendar}.
+ """
+ implements(ICalendar)
+
+ def __init__(self, home, name, resourceID, notifier):
+ """
+ Initialize a calendar pointing at a path on disk.
+
+ @param name: the subdirectory of calendarHome where this calendar
+ resides.
+ @type name: C{str}
+
+ @param calendarHome: the home containing this calendar.
+ @type calendarHome: L{CalendarHome}
+
+ @param realName: If this calendar was just created, the name which it
+ will eventually have on disk.
+ @type realName: C{str}
+ """
+ super(Calendar, self).__init__(home, name, resourceID, notifier)
+
+ self._index = PostgresLegacyIndexEmulator(self)
+ self._invites = PostgresLegacyInvitesEmulator(self)
+ self._objectResourceClass = CalendarObject
+ self._bindTable = CALENDAR_BIND_TABLE
+ self._homeChildTable = CALENDAR_TABLE
+ self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+ self._objectTable = CALENDAR_OBJECT_TABLE
+
+
+ @property
+ def _calendarHome(self):
+ return self._home
+
+
+ def resourceType(self):
+ return ResourceType.calendar #@UndefinedVariable
+
+
+ ownerCalendarHome = CommonHomeChild.ownerHome
+ calendarObjects = CommonHomeChild.objectResources
+ listCalendarObjects = CommonHomeChild.listObjectResources
+ calendarObjectWithName = CommonHomeChild.objectResourceWithName
+ calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
+ createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
+ removeCalendarObjectWithName = CommonHomeChild.removeObjectResourceWithName
+ removeCalendarObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+ calendarObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+ def calendarObjectsInTimeRange(self, start, end, timeZone):
+ raise NotImplementedError()
+
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ PropertyName.fromElement(caldavxml.CalendarDescription),
+ PropertyName.fromElement(caldavxml.CalendarTimeZone),
+ ),
+ (
+ PropertyName.fromElement(customxml.GETCTag),
+ PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
+ PropertyName.fromElement(caldavxml.ScheduleCalendarTransp),
+ ),
+ )
+
+ def contentType(self):
+ """
+ The content type of Calendar objects is text/calendar.
+ """
+ return MimeType.fromString("text/calendar; charset=utf-8")
+
+#
+# Duration into the future through which recurrences are expanded in the index
+# by default. This is a caching parameter which affects the size of the index;
+# it does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+default_future_expansion_duration = datetime.timedelta(days=365 * 1)
+
+#
+# Maximum duration into the future through which recurrences are expanded in the
+# index. This is a caching parameter which affects the size of the index; it
+# does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+# When a search is performed on a time span that goes beyond that which is
+# expanded in the index, we have to open each resource which may have data in
+# that time period. In order to avoid doing that multiple times, we want to
+# cache those results. However, we don't necessarily want to cache all
+# occurrences into some obscenely far-in-the-future date, so we cap the caching
+# period. Searches beyond this period will always be relatively expensive for
+# resources with occurrences beyond this period.
+#
+maximum_future_expansion_duration = datetime.timedelta(days=365 * 5)
+
+icalfbtype_to_indexfbtype = {
+ "UNKNOWN" : 0,
+ "FREE" : 1,
+ "BUSY" : 2,
+ "BUSY-UNAVAILABLE": 3,
+ "BUSY-TENTATIVE" : 4,
+}
+
+indexfbtype_to_icalfbtype = {
+ 0: '?',
+ 1: 'F',
+ 2: 'B',
+ 3: 'U',
+ 4: 'T',
+}
+
+def _pathToName(path):
+ return path.rsplit(".", 1)[0].split("-", 3)[-1]
+
+class CalendarObject(CommonObjectResource):
+ implements(ICalendarObject)
+
+ def __init__(self, name, calendar, resid):
+ super(CalendarObject, self).__init__(name, calendar, resid)
+
+ self._objectTable = CALENDAR_OBJECT_TABLE
+
+ @property
+ def _calendar(self):
+ return self._parentCollection
+
+ def calendar(self):
+ return self._calendar
+
+ def setComponent(self, component, inserting=False):
+ validateCalendarComponent(self, self._calendar, component, inserting)
+
+ self.updateDatabase(component, inserting=inserting)
+ if inserting:
+ self._calendar._insertRevision(self._name)
+ else:
+ self._calendar._updateRevision(self._name)
+
+ if self._calendar._notifier:
+ self._txn.postCommit(self._calendar._notifier.notify)
+
+ def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
+ """
+ Update the database tables for the new data being written.
+
+ @param component: calendar data to store
+ @type component: L{Component}
+ """
+
+ # Decide how far to expand based on the component
+ master = component.masterComponent()
+ if master is None or not component.isRecurring() and not component.isRecurringUnbounded():
+ # When there is no master we have a set of overridden components - index them all.
+ # When there is one instance - index it.
+ # When bounded - index all.
+ expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+ else:
+ if expand_until:
+ expand = expand_until
+ else:
+ expand = datetime.date.today() + default_future_expansion_duration
+
+ if expand > (datetime.date.today() + maximum_future_expansion_duration):
+ raise IndexedSearchException
+
+ try:
+ instances = component.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
+ except InvalidOverriddenInstanceError, e:
+ self.log_err("Invalid instance %s when indexing %s in %s" % (e.rid, self._name, self.resource,))
+ raise
+
+ componentText = str(component)
+ self._objectText = componentText
+ organizer = component.getOrganizer()
+ if not organizer:
+ organizer = ""
+
+ # CALENDAR_OBJECT table update
+ if inserting:
+ self._resourceID = self._txn.execSQL(
+ """
+ insert into CALENDAR_OBJECT
+ (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
+ values
+ (%s, %s, %s, %s, %s, %s, %s, %s)
+ returning RESOURCE_ID
+ """,
+ # FIXME: correct ATTACHMENTS_MODE based on X-APPLE-
+ # DROPBOX
+ [
+ self._calendar._resourceID,
+ self._name,
+ componentText,
+ component.resourceUID(),
+ component.resourceType(),
+ _ATTACHMENTS_MODE_WRITE,
+ organizer,
+ normalizeForIndex(instances.limit) if instances.limit else None,
+ ]
+ )[0][0]
+ else:
+ self._txn.execSQL(
+ """
+ update CALENDAR_OBJECT set
+ (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
+ =
+ (%s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+ where RESOURCE_ID = %s
+ """,
+ # should really be filling out more fields: ORGANIZER,
+ # ORGANIZER_OBJECT, a correct ATTACHMENTS_MODE based on X-APPLE-
+ # DROPBOX
+ [
+ componentText,
+ component.resourceUID(),
+ component.resourceType(),
+ _ATTACHMENTS_MODE_WRITE,
+ organizer,
+ normalizeForIndex(instances.limit) if instances.limit else None,
+ self._resourceID
+ ]
+ )
+
+ # Need to wipe the existing time-range for this and rebuild
+ self._txn.execSQL(
+ """
+ delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
+ """,
+ [
+ self._resourceID,
+ ],
+ )
+
+
+ # CALENDAR_OBJECT table update
+ for key in instances:
+ instance = instances[key]
+ start = instance.start.replace(tzinfo=utc)
+ end = instance.end.replace(tzinfo=utc)
+ float = instance.start.tzinfo is None
+ transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
+ instanceid = self._txn.execSQL(
+ """
+ insert into TIME_RANGE
+ (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
+ values
+ (%s, %s, %s, %s, %s, %s, %s)
+ returning
+ INSTANCE_ID
+ """,
+ [
+ self._calendar._resourceID,
+ self._resourceID,
+ float,
+ start,
+ end,
+ icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
+ transp,
+ ],
+ )[0][0]
+ peruserdata = component.perUserTransparency(instance.rid)
+ for useruid, transp in peruserdata:
+ self._txn.execSQL(
+ """
+ insert into TRANSPARENCY
+ (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
+ values
+ (%s, %s, %s)
+ """,
+ [
+ instanceid,
+ useruid,
+ transp,
+ ],
+ )
+
+ # Special - for unbounded recurrence we insert a value for "infinity"
+ # that will allow an open-ended time-range to always match it.
+ if component.isRecurringUnbounded():
+ start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+ end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+ float = False
+ instanceid = self._txn.execSQL(
+ """
+ insert into TIME_RANGE
+ (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
+ values
+ (%s, %s, %s, %s, %s, %s, %s)
+ returning
+ INSTANCE_ID
+ """,
+ [
+ self._calendar._resourceID,
+ self._resourceID,
+ float,
+ start,
+ end,
+ icalfbtype_to_indexfbtype["UNKNOWN"],
+ True,
+ ],
+ )[0][0]
+ peruserdata = component.perUserTransparency(None)
+ for useruid, transp in peruserdata:
+ self._txn.execSQL(
+ """
+ insert into TRANSPARENCY
+ (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
+ values
+ (%s, %s, %s)
+ """,
+ [
+ instanceid,
+ useruid,
+ transp,
+ ],
+ )
+
+ def component(self):
+ return VComponent.fromString(self.iCalendarText())
+
+ def text(self):
+ if self._objectText is None:
+ text = self._txn.execSQL(
+ "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
+ "RESOURCE_ID = %s", [self._resourceID]
+ )[0][0]
+ self._objectText = text
+ return text
+ else:
+ return self._objectText
+
+ iCalendarText = text
+
+ def uid(self):
+ return self.component().resourceUID()
+
+ def name(self):
+ return self._name
+
+ def componentType(self):
+ return self.component().mainType()
+
+ def organizer(self):
+ return self.component().getOrganizer()
+
+ def createAttachmentWithName(self, name, contentType):
+ path = self._attachmentPath(name)
+ attachment = Attachment(self, path)
+ self._txn.execSQL("""
+ insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
+ SIZE, MD5, PATH)
+ values (%s, %s, %s, %s, %s)
+ """,
+ [
+ self._resourceID, generateContentType(contentType), 0, "",
+ attachment._pathValue()
+ ]
+ )
+ return attachment.store(contentType)
+
+ def removeAttachmentWithName(self, name):
+ attachment = Attachment(self, self._attachmentPath(name))
+ self._txn.postCommit(attachment._path.remove)
+ self._txn.execSQL("""
+ delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+ PATH = %s
+ """, [self._resourceID, attachment._pathValue()])
+
+ def attachmentWithName(self, name):
+ attachment = Attachment(self, self._attachmentPath(name))
+ if attachment._populate():
+ return attachment
+ else:
+ return None
+
+ def attendeesCanManageAttachments(self):
+ return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+
+ def dropboxID(self):
+ return dropboxIDFromCalendarObject(self)
+
+ def _attachmentPath(self, name):
+ attachmentRoot = self._txn._store.attachmentsPath
+ try:
+ attachmentRoot.createDirectory()
+ except:
+ pass
+ return attachmentRoot.child(
+ "%s-%s-%s-%s.attachment" % (
+ self._calendar._home.uid(), self._calendar.name(),
+ self.name(), name
+ )
+ )
+
+ def attachments(self):
+ rows = self._txn.execSQL("""
+ select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
+ """, [self._resourceID])
+ for row in rows:
+ demangledName = _pathToName(row[0])
+ yield self.attachmentWithName(demangledName)
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ ),
+ (
+ PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
+ PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
+ PropertyName.fromElement(caldavxml.ScheduleTag),
+ PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
+ PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
+ PropertyName.fromElement(caldavxml.Originator),
+ PropertyName.fromElement(caldavxml.Recipient),
+ PropertyName.fromElement(customxml.ScheduleChanges),
+ ),
+ )
+
+ # IDataStoreResource
+ def contentType(self):
+ """
+ The content type of Calendar objects is text/calendar.
+ """
+ return MimeType.fromString("text/calendar; charset=utf-8")
+
+class AttachmentStorageTransport(object):
+
+ implements(ITransport)
+
+ def __init__(self, attachment, contentType):
+ self.attachment = attachment
+ self.contentType = contentType
+ self.buf = ''
+ self.hash = hashlib.md5()
+
+
+ @property
+ def _txn(self):
+ return self.attachment._txn
+
+
+ def write(self, data):
+ self.buf += data
+ self.hash.update(data)
+
+
+ def loseConnection(self):
+ self.attachment._path.setContent(self.buf)
+ pathValue = self.attachment._pathValue()
+ contentTypeString = generateContentType(self.contentType)
+ self._txn.execSQL(
+ "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
+ "WHERE PATH = %s",
+ [contentTypeString, len(self.buf), self.hash.hexdigest(), pathValue]
+ )
+
+class Attachment(object):
+
+ implements(IAttachment)
+
+ def __init__(self, calendarObject, path):
+ self._calendarObject = calendarObject
+ self._path = path
+
+
+ @property
+ def _txn(self):
+ return self._calendarObject._txn
+
+
+ def _populate(self):
+ """
+ Execute necessary SQL queries to retrieve attributes.
+
+ @return: C{True} if this attachment exists, C{False} otherwise.
+ """
+ rows = self._txn.execSQL(
+ """
+ select CONTENT_TYPE, SIZE, MD5, extract(EPOCH from CREATED), extract(EPOCH from MODIFIED) from ATTACHMENT where PATH = %s
+ """, [self._pathValue()])
+ if not rows:
+ return False
+ self._contentType = MimeType.fromString(rows[0][0])
+ self._size = rows[0][1]
+ self._md5 = rows[0][2]
+ self._created = int(rows[0][3])
+ self._modified = int(rows[0][4])
+ return True
+
+
+ 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._txn._store.attachmentsPath
+ return '/'.join(self._path.segmentsFrom(root))
+
+ def properties(self):
+ pass # stub
+
+
+ def store(self, contentType):
+ return AttachmentStorageTransport(self, contentType)
+
+
+ def retrieve(self, protocol):
+ protocol.dataReceived(self._path.getContent())
+ protocol.connectionLost(Failure(ConnectionLost()))
+
+
+ # IDataStoreResource
+ def contentType(self):
+ return self._contentType
+
+
+ def md5(self):
+ return self._md5
+
+
+ def size(self):
+ return self._size
+
+
+ def created(self):
+ return self._created
+
+ def modified(self):
+ return self._modified
Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/__init__.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/__init__.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test -*-
+# -*- test-case-name: txdav.caldav.datastore.test -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,1202 +0,0 @@
-# -*- test-case-name: txcaldav.calendarstore -*-
-##
-# 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 common calendar store API functions.
-"""
-
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
- BrokenMethodImplementation, DoesNotImplement)
-
-from twisted.internet.defer import Deferred, inlineCallbacks
-from twisted.internet.protocol import Protocol
-
-from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
-from txdav.propertystore.base import PropertyName
-
-from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError, \
- ICommonTransaction
-from txdav.common.icommondatastore import InvalidObjectResourceError
-from txdav.common.icommondatastore import NoSuchHomeChildError
-from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
-from txdav.common.inotifications import INotificationObject
-
-from txcaldav.icalendarstore import (
- ICalendarObject, ICalendarHome,
- ICalendar, IAttachment, ICalendarTransaction)
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2.dav import davxml
-from twext.web2.http_headers import MimeType
-from twext.web2.dav.element.base import WebDAVUnknownElement
-from twext.python.vcomponent import VComponent
-
-from twistedcaldav.notify import Notifier
-from twistedcaldav.customxml import InviteNotification, InviteSummary
-
-storePath = FilePath(__file__).parent().child("calendar_store")
-
-homeRoot = storePath.child("ho").child("me").child("home1")
-
-cal1Root = homeRoot.child("calendar_1")
-
-calendar1_objectNames = [
- "1.ics",
- "2.ics",
- "3.ics",
-]
-
-
-home1_calendarNames = [
- "calendar_1",
- "calendar_2",
- "calendar_empty",
-]
-
-
-event4_text = (
- "BEGIN:VCALENDAR\r\n"
- "VERSION:2.0\r\n"
- "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
- "CALSCALE:GREGORIAN\r\n"
- "BEGIN:VTIMEZONE\r\n"
- "TZID:US/Pacific\r\n"
- "BEGIN:DAYLIGHT\r\n"
- "TZOFFSETFROM:-0800\r\n"
- "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n"
- "DTSTART:20070311T020000\r\n"
- "TZNAME:PDT\r\n"
- "TZOFFSETTO:-0700\r\n"
- "END:DAYLIGHT\r\n"
- "BEGIN:STANDARD\r\n"
- "TZOFFSETFROM:-0700\r\n"
- "RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n"
- "DTSTART:20071104T020000\r\n"
- "TZNAME:PST\r\n"
- "TZOFFSETTO:-0800\r\n"
- "END:STANDARD\r\n"
- "END:VTIMEZONE\r\n"
- "BEGIN:VEVENT\r\n"
- "CREATED:20100203T013849Z\r\n"
- "UID:uid4\r\n"
- "DTEND;TZID=US/Pacific:20100207T173000\r\n"
- "TRANSP:OPAQUE\r\n"
- "SUMMARY:New Event\r\n"
- "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
- "DTSTAMP:20100203T013909Z\r\n"
- "SEQUENCE:3\r\n"
- "BEGIN:VALARM\r\n"
- "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
- "TRIGGER:-PT20M\r\n"
- "ATTACH;VALUE=URI:Basso\r\n"
- "ACTION:AUDIO\r\n"
- "END:VALARM\r\n"
- "END:VEVENT\r\n"
- "END:VCALENDAR\r\n"
-)
-
-
-
-event4notCalDAV_text = (
- "BEGIN:VCALENDAR\r\n"
- "VERSION:2.0\r\n"
- "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
- "CALSCALE:GREGORIAN\r\n"
- "BEGIN:VEVENT\r\n"
- "CREATED:20100203T013849Z\r\n"
- "UID:4\r\n"
- "DTEND;TZID=US/Pacific:20100207T173000\r\n" # TZID without VTIMEZONE
- "TRANSP:OPAQUE\r\n"
- "SUMMARY:New Event\r\n"
- "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
- "DTSTAMP:20100203T013909Z\r\n"
- "SEQUENCE:3\r\n"
- "BEGIN:VALARM\r\n"
- "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
- "TRIGGER:-PT20M\r\n"
- "ATTACH;VALUE=URI:Basso\r\n"
- "ACTION:AUDIO\r\n"
- "END:VALARM\r\n"
- "END:VEVENT\r\n"
- "END:VCALENDAR\r\n"
-)
-
-
-
-event1modified_text = event4_text.replace(
- "\r\nUID:uid4\r\n",
- "\r\nUID:uid1\r\n"
-)
-
-
-
-def assertProvides(testCase, interface, provider):
- """
- Verify that C{provider} properly provides C{interface}
-
- @type interface: L{zope.interface.Interface}
- @type provider: C{provider}
- """
- try:
- verifyObject(interface, provider)
- except BrokenMethodImplementation, e:
- testCase.fail(e)
- except DoesNotImplement, e:
- testCase.fail("%r does not provide %s.%s" %
- (provider, interface.__module__, interface.getName()))
-
-
-class CommonTests(object):
- """
- Tests for common functionality of interfaces defined in
- L{txcaldav.icalendarstore}.
- """
-
- requirements = {
- "home1": {
- "calendar_1": {
- "1.ics": cal1Root.child("1.ics").getContent(),
- "2.ics": cal1Root.child("2.ics").getContent(),
- "3.ics": cal1Root.child("3.ics").getContent()
- },
- "calendar_2": {},
- "calendar_empty": {},
- "not_a_calendar": None
- },
- "not_a_home": None
- }
-
- def storeUnderTest(self):
- """
- Subclasses must override this to return an L{ICommonDataStore} provider
- which adheres to the structure detailed by L{CommonTests.requirements}.
- This attribute is a dict of dict of dicts; the outermost layer
- representing UIDs mapping to calendar homes, then calendar names mapping
- to calendar collections, and finally calendar object names mapping to
- calendar object text.
- """
- raise NotImplementedError()
-
-
- lastTransaction = None
- savedStore = None
-
- def transactionUnderTest(self):
- """
- Create a transaction from C{storeUnderTest} and save it as
- C[lastTransaction}. Also makes sure to use the same store, saving the
- value from C{storeUnderTest}.
- """
- if self.lastTransaction is not None:
- return self.lastTransaction
- if self.savedStore is None:
- self.savedStore = self.storeUnderTest()
- txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
- return txn
-
-
- def commit(self):
- """
- Commit the last transaction created from C{transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.commit()
- self.lastTransaction = None
-
-
- def abort(self):
- """
- Abort the last transaction created from C[transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.abort()
- self.lastTransaction = None
-
-
- def setUp(self):
- self.notifierFactory = StubNotifierFactory()
-
-
- def tearDown(self):
- if self.lastTransaction is not None:
- self.commit()
-
-
- def homeUnderTest(self):
- """
- Get the calendar home detailed by C{requirements['home1']}.
- """
- return self.transactionUnderTest().calendarHomeWithUID("home1")
-
-
- def calendarUnderTest(self):
- """
- Get the calendar detailed by C{requirements['home1']['calendar_1']}.
- """
- return self.homeUnderTest().calendarWithName("calendar_1")
-
-
- def calendarObjectUnderTest(self):
- """
- Get the calendar detailed by
- C{requirements['home1']['calendar_1']['1.ics']}.
- """
- return self.calendarUnderTest().calendarObjectWithName("1.ics")
-
-
- assertProvides = assertProvides
-
- def test_calendarStoreProvides(self):
- """
- The calendar store provides L{IDataStore} and its required attributes.
- """
- calendarStore = self.storeUnderTest()
- self.assertProvides(IDataStore, calendarStore)
-
-
- def test_transactionProvides(self):
- """
- The transactions generated by the calendar store provide
- L{ICommonStoreTransaction}, L{ICalendarTransaction}, and their
- respectively required attributes.
- """
- txn = self.transactionUnderTest()
- self.assertProvides(ICommonTransaction, txn)
- self.assertProvides(ICalendarTransaction, txn)
-
-
- def test_homeProvides(self):
- """
- The calendar homes generated by the calendar store provide
- L{ICalendarHome} and its required attributes.
- """
- self.assertProvides(ICalendarHome, self.homeUnderTest())
-
-
- def test_calendarProvides(self):
- """
- The calendars generated by the calendar store provide L{ICalendar} and
- its required attributes.
- """
- self.assertProvides(ICalendar, self.calendarUnderTest())
-
-
- def test_calendarObjectProvides(self):
- """
- The calendar objects generated by the calendar store provide
- L{ICalendarObject} and its required attributes.
- """
- self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
-
-
- def notificationUnderTest(self):
- txn = self.transactionUnderTest()
- notifications = txn.notificationsWithUID("home1")
- inviteNotification = InviteNotification()
- notifications.writeNotificationObject("abc", inviteNotification,
- inviteNotification.toxml())
- notificationObject = notifications.notificationObjectWithUID("abc")
- return notificationObject
-
-
- def test_notificationObjectProvides(self):
- """
- The objects retrieved from the notification home (the object returned
- from L{notificationsWithUID}) provide L{INotificationObject}.
- """
- notificationObject = self.notificationUnderTest()
- self.assertProvides(INotificationObject, notificationObject)
-
-
- def test_replaceNotification(self):
- """
- L{INotificationCollection.writeNotificationObject} will silently
- overwrite the notification object.
- """
- notifications = self.transactionUnderTest().notificationsWithUID(
- "home1"
- )
- inviteNotification = InviteNotification()
- notifications.writeNotificationObject("abc", inviteNotification,
- inviteNotification.toxml())
- inviteNotification2 = InviteNotification(InviteSummary("a summary"))
- notifications.writeNotificationObject(
- "abc", inviteNotification, inviteNotification2.toxml())
- abc = notifications.notificationObjectWithUID("abc")
- self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
-
-
- def test_notificationObjectModified(self):
- """
- The objects retrieved from the notification home have a C{modified}
- method which returns the timestamp of their last modification.
- """
- notification = self.notificationUnderTest()
- self.assertIsInstance(notification.modified(), int)
-
-
- def test_notificationObjectParent(self):
- """
- L{INotificationObject.notificationCollection} returns the
- L{INotificationCollection} that the object was retrieved from.
- """
- txn = self.transactionUnderTest()
- collection = txn.notificationsWithUID("home1")
- notification = self.notificationUnderTest()
- self.assertIdentical(collection, notification.notificationCollection())
-
-
- def test_notifierID(self):
- home = self.homeUnderTest()
- self.assertEquals(home.notifierID(), "home1")
- calendar = home.calendarWithName("calendar_1")
- self.assertEquals(calendar.notifierID(), "home1")
- self.assertEquals(calendar.notifierID(label="collection"), "home1/calendar_1")
-
-
- def test_calendarHomeWithUID_exists(self):
- """
- Finding an existing calendar home by UID results in an object that
- provides L{ICalendarHome} and has a C{uid()} method that returns the
- same value that was passed in.
- """
- calendarHome = (self.transactionUnderTest()
- .calendarHomeWithUID("home1"))
- self.assertEquals(calendarHome.uid(), "home1")
- self.assertProvides(ICalendarHome, calendarHome)
-
-
- def test_calendarHomeWithUID_absent(self):
- """
- L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
- when asked for a non-existent calendar home.
- """
- txn = self.transactionUnderTest()
- self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
-
-
- def test_calendarWithName_exists(self):
- """
- L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
- whose name matches the one passed in.
- """
- home = self.homeUnderTest()
- for name in home1_calendarNames:
- calendar = home.calendarWithName(name)
- if calendar is None:
- self.fail("calendar %r didn't exist" % (name,))
- self.assertProvides(ICalendar, calendar)
- self.assertEquals(calendar.name(), name)
-
-
- def test_calendarRename(self):
- """
- L{ICalendar.rename} changes the name of the L{ICalendar}.
- """
- home = self.homeUnderTest()
- calendar = home.calendarWithName("calendar_1")
- calendar.rename("some_other_name")
- def positiveAssertions():
- self.assertEquals(calendar.name(), "some_other_name")
- self.assertEquals(calendar, home.calendarWithName("some_other_name"))
- self.assertEquals(None, home.calendarWithName("calendar_1"))
- positiveAssertions()
- self.commit()
- home = self.homeUnderTest()
- calendar = home.calendarWithName("some_other_name")
- positiveAssertions()
- # FIXME: revert
- # FIXME: test for multiple renames
- # FIXME: test for conflicting renames (a->b, c->a in the same txn)
-
-
- def test_calendarWithName_absent(self):
- """
- L{ICalendarHome.calendarWithName} returns C{None} for calendars which
- do not exist.
- """
- self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
- None)
-
-
- def test_createCalendarWithName_absent(self):
- """
- L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
- can be retrieved with L{ICalendarHome.calendarWithName}.
- """
- home = self.homeUnderTest()
- name = "new"
- self.assertIdentical(home.calendarWithName(name), None)
- home.createCalendarWithName(name)
- self.assertNotIdentical(home.calendarWithName(name), None)
- def checkProperties():
- calendarProperties = home.calendarWithName(name).properties()
- self.assertEquals(
- calendarProperties[
- PropertyName.fromString(davxml.ResourceType.sname())
- ],
- davxml.ResourceType.calendar #@UndefinedVariable
- )
- checkProperties()
-
- self.commit()
-
- # Make sure notification fired after commit
- 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)
-
- # Sanity check: are the properties actually persisted? Check in
- # subsequent transaction.
- checkProperties()
-
- # FIXME: no independent testing of the property store's persistence
- # right now
-
-
- def test_createCalendarWithName_exists(self):
- """
- L{ICalendarHome.createCalendarWithName} raises
- L{CalendarAlreadyExistsError} when the name conflicts with an already-
- existing
- """
- for name in home1_calendarNames:
- self.assertRaises(
- HomeChildNameAlreadyExistsError,
- self.homeUnderTest().createCalendarWithName, name
- )
-
-
- def test_removeCalendarWithName_exists(self):
- """
- L{ICalendarHome.removeCalendarWithName} removes a calendar that already
- exists.
- """
- home = self.homeUnderTest()
-
- # FIXME: test transactions
- for name in home1_calendarNames:
- self.assertNotIdentical(home.calendarWithName(name), None)
- home.removeCalendarWithName(name)
- self.assertEquals(home.calendarWithName(name), None)
-
- self.commit()
-
- # Make sure notification fired after commit
- self.assertEquals(
- self.notifierFactory.history,
- [("update", "home1"), ("update", "home1"), ("update", "home1")]
- )
-
-
- def test_removeCalendarWithName_absent(self):
- """
- Attempt to remove an non-existing calendar should raise.
- """
- home = self.homeUnderTest()
- self.assertRaises(NoSuchHomeChildError,
- home.removeCalendarWithName, "xyzzy")
-
-
- def test_calendarObjects(self):
- """
- L{ICalendar.calendarObjects} will enumerate the calendar objects present
- in the filesystem, in name order, but skip those with hidden names.
- """
- calendar1 = self.calendarUnderTest()
- calendarObjects = list(calendar1.calendarObjects())
-
- for calendarObject in calendarObjects:
- self.assertProvides(ICalendarObject, calendarObject)
- self.assertEquals(
- calendar1.calendarObjectWithName(calendarObject.name()),
- calendarObject
- )
-
- self.assertEquals(
- set(list(o.name() for o in calendarObjects)),
- set(calendar1_objectNames)
- )
-
-
- def test_calendarObjectsWithRemovedObject(self):
- """
- L{ICalendar.calendarObjects} skips those objects which have been
- removed by L{Calendar.removeCalendarObjectWithName} in the same
- transaction, even if it has not yet been committed.
- """
- calendar1 = self.calendarUnderTest()
- calendar1.removeCalendarObjectWithName("2.ics")
- calendarObjects = list(calendar1.calendarObjects())
- self.assertEquals(set(o.name() for o in calendarObjects),
- set(calendar1_objectNames) - set(["2.ics"]))
-
-
- def test_ownerCalendarHome(self):
- """
- L{ICalendar.ownerCalendarHome} should match the home UID.
- """
- self.assertEquals(
- self.calendarUnderTest().ownerCalendarHome().uid(),
- self.homeUnderTest().uid()
- )
-
-
- def test_calendarObjectWithName_exists(self):
- """
- L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
- provider for calendars which already exist.
- """
- calendar1 = self.calendarUnderTest()
- for name in calendar1_objectNames:
- calendarObject = calendar1.calendarObjectWithName(name)
- self.assertProvides(ICalendarObject, calendarObject)
- self.assertEquals(calendarObject.name(), name)
- # FIXME: add more tests based on CommonTests.requirements
-
-
- def test_calendarObjectWithName_absent(self):
- """
- L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
- don't exist.
- """
- calendar1 = self.calendarUnderTest()
- self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
-
-
- def test_removeCalendarObjectWithUID_exists(self):
- """
- Remove an existing calendar object.
- """
- calendar = self.calendarUnderTest()
- for name in calendar1_objectNames:
- uid = (u'uid' + name.rstrip(".ics"))
- self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
- None)
- calendar.removeCalendarObjectWithUID(uid)
- self.assertEquals(
- calendar.calendarObjectWithUID(uid),
- None
- )
- self.assertEquals(
- calendar.calendarObjectWithName(name),
- None
- )
-
- # Make sure notifications are fired after commit
- self.commit()
- 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):
- """
- Remove an existing calendar object.
- """
- calendar = self.calendarUnderTest()
- for name in calendar1_objectNames:
- self.assertNotIdentical(
- calendar.calendarObjectWithName(name), None
- )
- calendar.removeCalendarObjectWithName(name)
- self.assertIdentical(
- calendar.calendarObjectWithName(name), None
- )
-
-
- def test_removeCalendarObjectWithName_absent(self):
- """
- Attempt to remove an non-existing calendar object should raise.
- """
- calendar = self.calendarUnderTest()
- self.assertRaises(
- NoSuchObjectResourceError,
- calendar.removeCalendarObjectWithName, "xyzzy"
- )
-
-
- def test_calendarName(self):
- """
- L{Calendar.name} reflects the name of the calendar.
- """
- self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
-
-
- def test_calendarObjectName(self):
- """
- L{ICalendarObject.name} reflects the name of the calendar object.
- """
- self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
-
-
- def test_component(self):
- """
- L{ICalendarObject.component} returns a L{VComponent} describing the
- calendar data underlying that calendar object.
- """
- component = self.calendarObjectUnderTest().component()
-
- self.failUnless(
- isinstance(component, VComponent),
- component
- )
-
- self.assertEquals(component.name(), "VCALENDAR")
- self.assertEquals(component.mainType(), "VEVENT")
- self.assertEquals(component.resourceUID(), "uid1")
-
-
- def test_iCalendarText(self):
- """
- L{ICalendarObject.iCalendarText} returns a C{str} describing the same
- data provided by L{ICalendarObject.component}.
- """
- text = self.calendarObjectUnderTest().iCalendarText()
- self.assertIsInstance(text, str)
- self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
- self.assertIn("\r\nUID:uid1\r\n", text)
- self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
-
-
- def test_calendarObjectUID(self):
- """
- L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
- of the calendar object's component.
- """
- self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
-
-
- def test_organizer(self):
- """
- L{ICalendarObject.organizer} returns a C{str} describing the calendar
- user address of the C{ORGANIZER} property of the calendar object's
- component.
- """
- self.assertEquals(
- self.calendarObjectUnderTest().organizer(),
- "mailto:wsanchez at apple.com"
- )
-
-
- def test_calendarObjectWithUID_absent(self):
- """
- L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
- don't exist.
- """
- calendar1 = self.calendarUnderTest()
- self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
-
-
- def test_calendars(self):
- """
- L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
- providers, which are consistent with the results from
- L{ICalendar.calendarWithName}.
- """
- # Add a dot directory to make sure we don't find it
- # self.home1._path.child(".foo").createDirectory()
- home = self.homeUnderTest()
- calendars = list(home.calendars())
-
- for calendar in calendars:
- self.assertProvides(ICalendar, calendar)
- self.assertEquals(calendar,
- home.calendarWithName(calendar.name()))
-
- self.assertEquals(
- list(c.name() for c in calendars),
- home1_calendarNames
- )
-
-
- def test_calendarsAfterAddCalendar(self):
- """
- L{ICalendarHome.calendars} includes calendars recently added with
- L{ICalendarHome.createCalendarWithName}.
- """
- home = self.homeUnderTest()
- before = set(x.name() for x in home.calendars())
- home.createCalendarWithName("new-name")
- after = set(x.name() for x in home.calendars())
- self.assertEquals(before | set(['new-name']), after)
-
-
- def test_createCalendarObjectWithName_absent(self):
- """
- L{ICalendar.createCalendarObjectWithName} creates a new
- L{ICalendarObject}.
- """
- calendar1 = self.calendarUnderTest()
- name = "4.ics"
- self.assertIdentical(calendar1.calendarObjectWithName(name), None)
- component = VComponent.fromString(event4_text)
- calendar1.createCalendarObjectWithName(name, component)
-
- calendarObject = calendar1.calendarObjectWithName(name)
- self.assertEquals(calendarObject.component(), component)
-
- self.commit()
-
- # Make sure notifications fire after commit
- self.assertEquals(
- self.notifierFactory.history,
- [
- ("update", "home1"),
- ("update", "home1/calendar_1"),
- ]
- )
-
-
- def test_createCalendarObjectWithName_exists(self):
- """
- L{ICalendar.createCalendarObjectWithName} raises
- L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
- given name already exists in that calendar.
- """
- cal = self.calendarUnderTest()
- comp = VComponent.fromString(event4_text)
- self.assertRaises(
- ObjectResourceNameAlreadyExistsError,
- cal.createCalendarObjectWithName,
- "1.ics", comp
- )
-
-
- def test_createCalendarObjectWithName_invalid(self):
- """
- L{ICalendar.createCalendarObjectWithName} raises
- L{InvalidCalendarComponentError} if presented with invalid iCalendar
- text.
- """
- self.assertRaises(
- InvalidObjectResourceError,
- self.calendarUnderTest().createCalendarObjectWithName,
- "new", VComponent.fromString(event4notCalDAV_text)
- )
-
-
- def test_setComponent_invalid(self):
- """
- L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
- presented with invalid iCalendar text.
- """
- calendarObject = self.calendarObjectUnderTest()
- self.assertRaises(
- InvalidObjectResourceError,
- calendarObject.setComponent,
- VComponent.fromString(event4notCalDAV_text)
- )
-
-
- def test_setComponent_uidchanged(self):
- """
- L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
- when given a L{VComponent} whose UID does not match its existing UID.
- """
- calendar1 = self.calendarUnderTest()
- component = VComponent.fromString(event4_text)
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- self.assertRaises(
- InvalidObjectResourceError,
- calendarObject.setComponent, component
- )
-
-
- def test_calendarHomeWithUID_create(self):
- """
- L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
- will create a calendar home that doesn't exist yet.
- """
- txn = self.transactionUnderTest()
- noHomeUID = "xyzzy"
- calendarHome = txn.calendarHomeWithUID(
- noHomeUID,
- create=True
- )
- def readOtherTxn():
- otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
- self.addCleanup(otherTxn.commit)
- return otherTxn.calendarHomeWithUID(noHomeUID)
- self.assertProvides(ICalendarHome, calendarHome)
- # Default calendar should be automatically created.
- self.assertProvides(ICalendar,
- calendarHome.calendarWithName("calendar"))
- # A concurrent transaction shouldn't be able to read it yet:
- self.assertIdentical(readOtherTxn(), None)
- self.commit()
- # But once it's committed, other transactions should see it.
- self.assertProvides(ICalendarHome, readOtherTxn())
-
-
- def test_setComponent(self):
- """
- L{CalendarObject.setComponent} changes the result of
- L{CalendarObject.component} within the same transaction.
- """
- component = VComponent.fromString(event1modified_text)
-
- calendar1 = self.calendarUnderTest()
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- oldComponent = calendarObject.component()
- self.assertNotEqual(component, oldComponent)
- calendarObject.setComponent(component)
- self.assertEquals(calendarObject.component(), component)
-
- # Also check a new instance
- calendarObject = calendar1.calendarObjectWithName("1.ics")
- self.assertEquals(calendarObject.component(), component)
-
- self.commit()
-
- # Make sure notification fired after commit
- self.assertEquals(
- self.notifierFactory.history,
- [
- ("update", "home1"),
- ("update", "home1/calendar_1"),
- ]
- )
-
-
- def checkPropertiesMethod(self, thunk):
- """
- Verify that the given object has a properties method that returns an
- L{IPropertyStore}.
- """
- properties = thunk.properties()
- self.assertProvides(IPropertyStore, properties)
-
-
- def test_homeProperties(self):
- """
- L{ICalendarHome.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.homeUnderTest())
-
-
- def test_calendarProperties(self):
- """
- L{ICalendar.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.calendarUnderTest())
-
-
- def test_calendarObjectProperties(self):
- """
- L{ICalendarObject.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.calendarObjectUnderTest())
-
-
- def test_newCalendarObjectProperties(self):
- """
- L{ICalendarObject.properties} returns an empty property store for a
- calendar object which has been created but not committed.
- """
- calendar = self.calendarUnderTest()
- calendar.createCalendarObjectWithName(
- "4.ics", VComponent.fromString(event4_text)
- )
- newEvent = calendar.calendarObjectWithName("4.ics")
- self.assertEquals(newEvent.properties().items(), [])
-
-
- def test_setComponentPreservesProperties(self):
- """
- L{ICalendarObject.setComponent} preserves properties.
-
- (Some implementations must go to extra trouble to provide this
- behavior; for example, file storage must copy extended attributes from
- the existing file to the temporary file replacing it.)
- """
- propertyName = PropertyName("http://example.com/ns", "example")
- propertyContent = WebDAVUnknownElement("sample content")
- propertyContent.name = propertyName.name
- propertyContent.namespace = propertyName.namespace
-
- self.calendarObjectUnderTest().properties()[
- propertyName] = propertyContent
- self.commit()
- # Sanity check; are properties even readable in a separate transaction?
- # Should probably be a separate test.
- self.assertEquals(
- self.calendarObjectUnderTest().properties()[propertyName],
- propertyContent)
- obj = self.calendarObjectUnderTest()
- event1_text = obj.iCalendarText()
- event1_text_withDifferentSubject = event1_text.replace(
- "SUMMARY:CalDAV protocol updates",
- "SUMMARY:Changed"
- )
- # Sanity check; make sure the test has the right idea of the subject.
- self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
- newComponent = VComponent.fromString(event1_text_withDifferentSubject)
- obj.setComponent(newComponent)
-
- # Putting everything into a separate transaction to account for any
- # caching that may take place.
- self.commit()
- self.assertEquals(
- self.calendarObjectUnderTest().properties()[propertyName],
- propertyContent
- )
-
-
- eventWithDropbox = "\r\n".join("""
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-PRODID:-//Example Inc.//Example Calendar//EN
-VERSION:2.0
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20040110T032845Z
-TZID:US/Eastern
-BEGIN:DAYLIGHT
-DTSTART:20000404T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20001026T020000
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-DTSTAMP:20051222T205953Z
-CREATED:20060101T150000Z
-DTSTART;TZID=US/Eastern:20060101T100000
-DURATION:PT1H
-SUMMARY:event 1
-UID:event1 at ninevah.local
-ORGANIZER:user01
-ATTENDEE;PARTSTAT=ACCEPTED:user01
-ATTACH;VALUE=URI:/calendars/users/home1/some-dropbox-id/some-dropbox-id/caldavd.plist
-X-APPLE-DROPBOX:/calendars/users/home1/dropbox/some-dropbox-id
-END:VEVENT
-END:VCALENDAR
- """.strip().split("\n"))
-
- def test_dropboxID(self):
- """
- L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
- -APPLE-DROPBOX property, if available.
- """
- cal = self.calendarUnderTest()
- cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
- self.eventWithDropbox
- )
- )
- obj = cal.calendarObjectWithName("drop.ics")
- self.assertEquals(obj.dropboxID(), "some-dropbox-id")
-
-
- def test_indexByDropboxProperty(self):
- """
- L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
- object in the calendar home with the given final segment in its C{X
- -APPLE-DROPBOX} property URI.
- """
- objName = "with-dropbox.ics"
- cal = self.calendarUnderTest()
- cal.createCalendarObjectWithName(
- objName, VComponent.fromString(
- self.eventWithDropbox
- )
- )
- self.commit()
- home = self.homeUnderTest()
- cal = self.calendarUnderTest()
- fromName = cal.calendarObjectWithName(objName)
- fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
- self.assertEquals(fromName, fromDropbox)
-
-
- @inlineCallbacks
- def createAttachmentTest(self, refresh):
- """
- Common logic for attachment-creation tests.
- """
- obj = self.calendarObjectUnderTest()
- t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
- t.write("new attachment")
- t.write(" text")
- t.loseConnection()
- obj = refresh(obj)
- class CaptureProtocol(Protocol):
- buf = ''
- def dataReceived(self, data):
- self.buf += data
- def connectionLost(self, reason):
- self.deferred.callback(self.buf)
- capture = CaptureProtocol()
- capture.deferred = Deferred()
- attachment = obj.attachmentWithName("new.attachment")
- self.assertProvides(IAttachment, attachment)
- attachment.retrieve(capture)
- data = yield capture.deferred
- self.assertEquals(data, "new attachment text")
- contentType = attachment.contentType()
- self.assertIsInstance(contentType, MimeType)
- self.assertEquals(contentType, MimeType("text", "x-fixture"))
- self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
- self.assertEquals(
- [attachment.name() for attachment in obj.attachments()],
- ['new.attachment']
- )
-
-
- def test_createAttachment(self):
- """
- L{ICalendarObject.createAttachmentWithName} will store an
- L{IAttachment} object that can be retrieved by
- L{ICalendarObject.attachmentWithName}.
- """
- return self.createAttachmentTest(lambda x: x)
-
-
- def test_createAttachmentCommit(self):
- """
- L{ICalendarObject.createAttachmentWithName} will store an
- L{IAttachment} object that can be retrieved by
- L{ICalendarObject.attachmentWithName} in subsequent transactions.
- """
- def refresh(obj):
- self.commit()
- return self.calendarObjectUnderTest()
- return self.createAttachmentTest(refresh)
-
-
- def test_removeAttachmentWithName(self, refresh=lambda x:x):
- """
- L{ICalendarObject.removeAttachmentWithName} will remove the calendar
- object with the given name.
- """
- def deleteIt(ignored):
- obj = self.calendarObjectUnderTest()
- obj.removeAttachmentWithName("new.attachment")
- obj = refresh(obj)
- self.assertIdentical(
- None, obj.attachmentWithName("new.attachment")
- )
- self.assertEquals(list(obj.attachments()), [])
- return self.test_createAttachmentCommit().addCallback(deleteIt)
-
-
- def test_removeAttachmentWithNameCommit(self):
- """
- L{ICalendarObject.removeAttachmentWithName} will remove the calendar
- object with the given name. (After commit, it will still be gone.)
- """
- def refresh(obj):
- self.commit()
- return self.calendarObjectUnderTest()
- return self.test_removeAttachmentWithName(refresh)
-
-
- def test_noDropboxCalendar(self):
- """
- L{ICalendarObject.createAttachmentWithName} may create a directory
- named 'dropbox', but this should not be seen as a calendar by
- L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
- """
- obj = self.calendarObjectUnderTest()
- t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
- t.write("new attachment text")
- t.loseConnection()
- self.commit()
- self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
- None)
- self.assertEquals(
- set([n.name() for n in self.homeUnderTest().calendars()]),
- set(home1_calendarNames))
-
-
- def test_finishedOnCommit(self):
- """
- Calling L{ITransaction.abort} or L{ITransaction.commit} after
- L{ITransaction.commit} has already been called raises an
- L{AlreadyFinishedError}.
- """
- self.calendarObjectUnderTest()
- txn = self.lastTransaction
- self.commit()
- self.assertRaises(AlreadyFinishedError, txn.commit)
- self.assertRaises(AlreadyFinishedError, txn.abort)
-
-
- def test_dontLeakCalendars(self):
- """
- Calendars in one user's calendar home should not show up in another
- user's calendar home.
- """
- home2 = self.transactionUnderTest().calendarHomeWithUID(
- "home2", create=True)
- self.assertIdentical(home2.calendarWithName("calendar_1"), None)
-
-
- def test_dontLeakObjects(self):
- """
- Calendar objects in one user's calendar should not show up in another
- user's via uid or name queries.
- """
- home1 = self.homeUnderTest()
- home2 = self.transactionUnderTest().calendarHomeWithUID(
- "home2", create=True)
- calendar1 = home1.calendarWithName("calendar_1")
- calendar2 = home2.calendarWithName("calendar")
- objects = list(home2.calendarWithName("calendar").calendarObjects())
- self.assertEquals(objects, [])
- for resourceName in self.requirements['home1']['calendar_1'].keys():
- obj = calendar1.calendarObjectWithName(resourceName)
- self.assertIdentical(
- calendar2.calendarObjectWithName(resourceName), None)
- self.assertIdentical(
- calendar2.calendarObjectWithUID(obj.uid()), None)
-
-
-
-class StubNotifierFactory(object):
-
- """ For testing push notifications without an XMPP server """
-
- def __init__(self):
- self.reset()
-
- def newNotifier(self, label="default", id=None):
- return Notifier(self, label=label, id=id)
-
- def send(self, op, id):
- self.history.append((op, id))
-
- def reset(self):
- self.history = []
Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/common.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,1202 @@
+# -*- test-case-name: txdav.caldav.datastore -*-
+##
+# 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 common calendar store API functions.
+"""
+
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import (
+ BrokenMethodImplementation, DoesNotImplement)
+
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.protocol import Protocol
+
+from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
+from txdav.base.propertystore.base import PropertyName
+
+from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError, \
+ ICommonTransaction
+from txdav.common.icommondatastore import InvalidObjectResourceError
+from txdav.common.icommondatastore import NoSuchHomeChildError
+from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
+from txdav.common.inotifications import INotificationObject
+
+from txdav.caldav.icalendarstore import (
+ ICalendarObject, ICalendarHome,
+ ICalendar, IAttachment, ICalendarTransaction)
+
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav import davxml
+from twext.web2.http_headers import MimeType
+from twext.web2.dav.element.base import WebDAVUnknownElement
+from twext.python.vcomponent import VComponent
+
+from twistedcaldav.notify import Notifier
+from twistedcaldav.customxml import InviteNotification, InviteSummary
+
+storePath = FilePath(__file__).parent().child("calendar_store")
+
+homeRoot = storePath.child("ho").child("me").child("home1")
+
+cal1Root = homeRoot.child("calendar_1")
+
+calendar1_objectNames = [
+ "1.ics",
+ "2.ics",
+ "3.ics",
+]
+
+
+home1_calendarNames = [
+ "calendar_1",
+ "calendar_2",
+ "calendar_empty",
+]
+
+
+event4_text = (
+ "BEGIN:VCALENDAR\r\n"
+ "VERSION:2.0\r\n"
+ "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
+ "CALSCALE:GREGORIAN\r\n"
+ "BEGIN:VTIMEZONE\r\n"
+ "TZID:US/Pacific\r\n"
+ "BEGIN:DAYLIGHT\r\n"
+ "TZOFFSETFROM:-0800\r\n"
+ "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n"
+ "DTSTART:20070311T020000\r\n"
+ "TZNAME:PDT\r\n"
+ "TZOFFSETTO:-0700\r\n"
+ "END:DAYLIGHT\r\n"
+ "BEGIN:STANDARD\r\n"
+ "TZOFFSETFROM:-0700\r\n"
+ "RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n"
+ "DTSTART:20071104T020000\r\n"
+ "TZNAME:PST\r\n"
+ "TZOFFSETTO:-0800\r\n"
+ "END:STANDARD\r\n"
+ "END:VTIMEZONE\r\n"
+ "BEGIN:VEVENT\r\n"
+ "CREATED:20100203T013849Z\r\n"
+ "UID:uid4\r\n"
+ "DTEND;TZID=US/Pacific:20100207T173000\r\n"
+ "TRANSP:OPAQUE\r\n"
+ "SUMMARY:New Event\r\n"
+ "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
+ "DTSTAMP:20100203T013909Z\r\n"
+ "SEQUENCE:3\r\n"
+ "BEGIN:VALARM\r\n"
+ "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
+ "TRIGGER:-PT20M\r\n"
+ "ATTACH;VALUE=URI:Basso\r\n"
+ "ACTION:AUDIO\r\n"
+ "END:VALARM\r\n"
+ "END:VEVENT\r\n"
+ "END:VCALENDAR\r\n"
+)
+
+
+
+event4notCalDAV_text = (
+ "BEGIN:VCALENDAR\r\n"
+ "VERSION:2.0\r\n"
+ "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
+ "CALSCALE:GREGORIAN\r\n"
+ "BEGIN:VEVENT\r\n"
+ "CREATED:20100203T013849Z\r\n"
+ "UID:4\r\n"
+ "DTEND;TZID=US/Pacific:20100207T173000\r\n" # TZID without VTIMEZONE
+ "TRANSP:OPAQUE\r\n"
+ "SUMMARY:New Event\r\n"
+ "DTSTART;TZID=US/Pacific:20100207T170000\r\n"
+ "DTSTAMP:20100203T013909Z\r\n"
+ "SEQUENCE:3\r\n"
+ "BEGIN:VALARM\r\n"
+ "X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1\r\n"
+ "TRIGGER:-PT20M\r\n"
+ "ATTACH;VALUE=URI:Basso\r\n"
+ "ACTION:AUDIO\r\n"
+ "END:VALARM\r\n"
+ "END:VEVENT\r\n"
+ "END:VCALENDAR\r\n"
+)
+
+
+
+event1modified_text = event4_text.replace(
+ "\r\nUID:uid4\r\n",
+ "\r\nUID:uid1\r\n"
+)
+
+
+
+def assertProvides(testCase, interface, provider):
+ """
+ Verify that C{provider} properly provides C{interface}
+
+ @type interface: L{zope.interface.Interface}
+ @type provider: C{provider}
+ """
+ try:
+ verifyObject(interface, provider)
+ except BrokenMethodImplementation, e:
+ testCase.fail(e)
+ except DoesNotImplement, e:
+ testCase.fail("%r does not provide %s.%s" %
+ (provider, interface.__module__, interface.getName()))
+
+
+class CommonTests(object):
+ """
+ Tests for common functionality of interfaces defined in
+ L{txdav.caldav.icalendarstore}.
+ """
+
+ requirements = {
+ "home1": {
+ "calendar_1": {
+ "1.ics": cal1Root.child("1.ics").getContent(),
+ "2.ics": cal1Root.child("2.ics").getContent(),
+ "3.ics": cal1Root.child("3.ics").getContent()
+ },
+ "calendar_2": {},
+ "calendar_empty": {},
+ "not_a_calendar": None
+ },
+ "not_a_home": None
+ }
+
+ def storeUnderTest(self):
+ """
+ Subclasses must override this to return an L{ICommonDataStore} provider
+ which adheres to the structure detailed by L{CommonTests.requirements}.
+ This attribute is a dict of dict of dicts; the outermost layer
+ representing UIDs mapping to calendar homes, then calendar names mapping
+ to calendar collections, and finally calendar object names mapping to
+ calendar object text.
+ """
+ raise NotImplementedError()
+
+
+ lastTransaction = None
+ savedStore = None
+
+ def transactionUnderTest(self):
+ """
+ Create a transaction from C{storeUnderTest} and save it as
+ C[lastTransaction}. Also makes sure to use the same store, saving the
+ value from C{storeUnderTest}.
+ """
+ if self.lastTransaction is not None:
+ return self.lastTransaction
+ if self.savedStore is None:
+ self.savedStore = self.storeUnderTest()
+ txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
+ return txn
+
+
+ def commit(self):
+ """
+ Commit the last transaction created from C{transactionUnderTest}, and
+ clear it.
+ """
+ self.lastTransaction.commit()
+ self.lastTransaction = None
+
+
+ def abort(self):
+ """
+ Abort the last transaction created from C[transactionUnderTest}, and
+ clear it.
+ """
+ self.lastTransaction.abort()
+ self.lastTransaction = None
+
+
+ def setUp(self):
+ self.notifierFactory = StubNotifierFactory()
+
+
+ def tearDown(self):
+ if self.lastTransaction is not None:
+ self.commit()
+
+
+ def homeUnderTest(self):
+ """
+ Get the calendar home detailed by C{requirements['home1']}.
+ """
+ return self.transactionUnderTest().calendarHomeWithUID("home1")
+
+
+ def calendarUnderTest(self):
+ """
+ Get the calendar detailed by C{requirements['home1']['calendar_1']}.
+ """
+ return self.homeUnderTest().calendarWithName("calendar_1")
+
+
+ def calendarObjectUnderTest(self):
+ """
+ Get the calendar detailed by
+ C{requirements['home1']['calendar_1']['1.ics']}.
+ """
+ return self.calendarUnderTest().calendarObjectWithName("1.ics")
+
+
+ assertProvides = assertProvides
+
+ def test_calendarStoreProvides(self):
+ """
+ The calendar store provides L{IDataStore} and its required attributes.
+ """
+ calendarStore = self.storeUnderTest()
+ self.assertProvides(IDataStore, calendarStore)
+
+
+ def test_transactionProvides(self):
+ """
+ The transactions generated by the calendar store provide
+ L{ICommonStoreTransaction}, L{ICalendarTransaction}, and their
+ respectively required attributes.
+ """
+ txn = self.transactionUnderTest()
+ self.assertProvides(ICommonTransaction, txn)
+ self.assertProvides(ICalendarTransaction, txn)
+
+
+ def test_homeProvides(self):
+ """
+ The calendar homes generated by the calendar store provide
+ L{ICalendarHome} and its required attributes.
+ """
+ self.assertProvides(ICalendarHome, self.homeUnderTest())
+
+
+ def test_calendarProvides(self):
+ """
+ The calendars generated by the calendar store provide L{ICalendar} and
+ its required attributes.
+ """
+ self.assertProvides(ICalendar, self.calendarUnderTest())
+
+
+ def test_calendarObjectProvides(self):
+ """
+ The calendar objects generated by the calendar store provide
+ L{ICalendarObject} and its required attributes.
+ """
+ self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
+
+
+ def notificationUnderTest(self):
+ txn = self.transactionUnderTest()
+ notifications = txn.notificationsWithUID("home1")
+ inviteNotification = InviteNotification()
+ notifications.writeNotificationObject("abc", inviteNotification,
+ inviteNotification.toxml())
+ notificationObject = notifications.notificationObjectWithUID("abc")
+ return notificationObject
+
+
+ def test_notificationObjectProvides(self):
+ """
+ The objects retrieved from the notification home (the object returned
+ from L{notificationsWithUID}) provide L{INotificationObject}.
+ """
+ notificationObject = self.notificationUnderTest()
+ self.assertProvides(INotificationObject, notificationObject)
+
+
+ def test_replaceNotification(self):
+ """
+ L{INotificationCollection.writeNotificationObject} will silently
+ overwrite the notification object.
+ """
+ notifications = self.transactionUnderTest().notificationsWithUID(
+ "home1"
+ )
+ inviteNotification = InviteNotification()
+ notifications.writeNotificationObject("abc", inviteNotification,
+ inviteNotification.toxml())
+ inviteNotification2 = InviteNotification(InviteSummary("a summary"))
+ notifications.writeNotificationObject(
+ "abc", inviteNotification, inviteNotification2.toxml())
+ abc = notifications.notificationObjectWithUID("abc")
+ self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
+
+
+ def test_notificationObjectModified(self):
+ """
+ The objects retrieved from the notification home have a C{modified}
+ method which returns the timestamp of their last modification.
+ """
+ notification = self.notificationUnderTest()
+ self.assertIsInstance(notification.modified(), int)
+
+
+ def test_notificationObjectParent(self):
+ """
+ L{INotificationObject.notificationCollection} returns the
+ L{INotificationCollection} that the object was retrieved from.
+ """
+ txn = self.transactionUnderTest()
+ collection = txn.notificationsWithUID("home1")
+ notification = self.notificationUnderTest()
+ self.assertIdentical(collection, notification.notificationCollection())
+
+
+ def test_notifierID(self):
+ home = self.homeUnderTest()
+ self.assertEquals(home.notifierID(), "home1")
+ calendar = home.calendarWithName("calendar_1")
+ self.assertEquals(calendar.notifierID(), "home1")
+ self.assertEquals(calendar.notifierID(label="collection"), "home1/calendar_1")
+
+
+ def test_calendarHomeWithUID_exists(self):
+ """
+ Finding an existing calendar home by UID results in an object that
+ provides L{ICalendarHome} and has a C{uid()} method that returns the
+ same value that was passed in.
+ """
+ calendarHome = (self.transactionUnderTest()
+ .calendarHomeWithUID("home1"))
+ self.assertEquals(calendarHome.uid(), "home1")
+ self.assertProvides(ICalendarHome, calendarHome)
+
+
+ def test_calendarHomeWithUID_absent(self):
+ """
+ L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
+ when asked for a non-existent calendar home.
+ """
+ txn = self.transactionUnderTest()
+ self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
+
+
+ def test_calendarWithName_exists(self):
+ """
+ L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
+ whose name matches the one passed in.
+ """
+ home = self.homeUnderTest()
+ for name in home1_calendarNames:
+ calendar = home.calendarWithName(name)
+ if calendar is None:
+ self.fail("calendar %r didn't exist" % (name,))
+ self.assertProvides(ICalendar, calendar)
+ self.assertEquals(calendar.name(), name)
+
+
+ def test_calendarRename(self):
+ """
+ L{ICalendar.rename} changes the name of the L{ICalendar}.
+ """
+ home = self.homeUnderTest()
+ calendar = home.calendarWithName("calendar_1")
+ calendar.rename("some_other_name")
+ def positiveAssertions():
+ self.assertEquals(calendar.name(), "some_other_name")
+ self.assertEquals(calendar, home.calendarWithName("some_other_name"))
+ self.assertEquals(None, home.calendarWithName("calendar_1"))
+ positiveAssertions()
+ self.commit()
+ home = self.homeUnderTest()
+ calendar = home.calendarWithName("some_other_name")
+ positiveAssertions()
+ # FIXME: revert
+ # FIXME: test for multiple renames
+ # FIXME: test for conflicting renames (a->b, c->a in the same txn)
+
+
+ def test_calendarWithName_absent(self):
+ """
+ L{ICalendarHome.calendarWithName} returns C{None} for calendars which
+ do not exist.
+ """
+ self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
+ None)
+
+
+ def test_createCalendarWithName_absent(self):
+ """
+ L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
+ can be retrieved with L{ICalendarHome.calendarWithName}.
+ """
+ home = self.homeUnderTest()
+ name = "new"
+ self.assertIdentical(home.calendarWithName(name), None)
+ home.createCalendarWithName(name)
+ self.assertNotIdentical(home.calendarWithName(name), None)
+ def checkProperties():
+ calendarProperties = home.calendarWithName(name).properties()
+ self.assertEquals(
+ calendarProperties[
+ PropertyName.fromString(davxml.ResourceType.sname())
+ ],
+ davxml.ResourceType.calendar #@UndefinedVariable
+ )
+ checkProperties()
+
+ self.commit()
+
+ # Make sure notification fired after commit
+ 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)
+
+ # Sanity check: are the properties actually persisted? Check in
+ # subsequent transaction.
+ checkProperties()
+
+ # FIXME: no independent testing of the property store's persistence
+ # right now
+
+
+ def test_createCalendarWithName_exists(self):
+ """
+ L{ICalendarHome.createCalendarWithName} raises
+ L{CalendarAlreadyExistsError} when the name conflicts with an already-
+ existing
+ """
+ for name in home1_calendarNames:
+ self.assertRaises(
+ HomeChildNameAlreadyExistsError,
+ self.homeUnderTest().createCalendarWithName, name
+ )
+
+
+ def test_removeCalendarWithName_exists(self):
+ """
+ L{ICalendarHome.removeCalendarWithName} removes a calendar that already
+ exists.
+ """
+ home = self.homeUnderTest()
+
+ # FIXME: test transactions
+ for name in home1_calendarNames:
+ self.assertNotIdentical(home.calendarWithName(name), None)
+ home.removeCalendarWithName(name)
+ self.assertEquals(home.calendarWithName(name), None)
+
+ self.commit()
+
+ # Make sure notification fired after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [("update", "home1"), ("update", "home1"), ("update", "home1")]
+ )
+
+
+ def test_removeCalendarWithName_absent(self):
+ """
+ Attempt to remove an non-existing calendar should raise.
+ """
+ home = self.homeUnderTest()
+ self.assertRaises(NoSuchHomeChildError,
+ home.removeCalendarWithName, "xyzzy")
+
+
+ def test_calendarObjects(self):
+ """
+ L{ICalendar.calendarObjects} will enumerate the calendar objects present
+ in the filesystem, in name order, but skip those with hidden names.
+ """
+ calendar1 = self.calendarUnderTest()
+ calendarObjects = list(calendar1.calendarObjects())
+
+ for calendarObject in calendarObjects:
+ self.assertProvides(ICalendarObject, calendarObject)
+ self.assertEquals(
+ calendar1.calendarObjectWithName(calendarObject.name()),
+ calendarObject
+ )
+
+ self.assertEquals(
+ set(list(o.name() for o in calendarObjects)),
+ set(calendar1_objectNames)
+ )
+
+
+ def test_calendarObjectsWithRemovedObject(self):
+ """
+ L{ICalendar.calendarObjects} skips those objects which have been
+ removed by L{Calendar.removeCalendarObjectWithName} in the same
+ transaction, even if it has not yet been committed.
+ """
+ calendar1 = self.calendarUnderTest()
+ calendar1.removeCalendarObjectWithName("2.ics")
+ calendarObjects = list(calendar1.calendarObjects())
+ self.assertEquals(set(o.name() for o in calendarObjects),
+ set(calendar1_objectNames) - set(["2.ics"]))
+
+
+ def test_ownerCalendarHome(self):
+ """
+ L{ICalendar.ownerCalendarHome} should match the home UID.
+ """
+ self.assertEquals(
+ self.calendarUnderTest().ownerCalendarHome().uid(),
+ self.homeUnderTest().uid()
+ )
+
+
+ def test_calendarObjectWithName_exists(self):
+ """
+ L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
+ provider for calendars which already exist.
+ """
+ calendar1 = self.calendarUnderTest()
+ for name in calendar1_objectNames:
+ calendarObject = calendar1.calendarObjectWithName(name)
+ self.assertProvides(ICalendarObject, calendarObject)
+ self.assertEquals(calendarObject.name(), name)
+ # FIXME: add more tests based on CommonTests.requirements
+
+
+ def test_calendarObjectWithName_absent(self):
+ """
+ L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
+ don't exist.
+ """
+ calendar1 = self.calendarUnderTest()
+ self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
+
+
+ def test_removeCalendarObjectWithUID_exists(self):
+ """
+ Remove an existing calendar object.
+ """
+ calendar = self.calendarUnderTest()
+ for name in calendar1_objectNames:
+ uid = (u'uid' + name.rstrip(".ics"))
+ self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
+ None)
+ calendar.removeCalendarObjectWithUID(uid)
+ self.assertEquals(
+ calendar.calendarObjectWithUID(uid),
+ None
+ )
+ self.assertEquals(
+ calendar.calendarObjectWithName(name),
+ None
+ )
+
+ # Make sure notifications are fired after commit
+ self.commit()
+ 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):
+ """
+ Remove an existing calendar object.
+ """
+ calendar = self.calendarUnderTest()
+ for name in calendar1_objectNames:
+ self.assertNotIdentical(
+ calendar.calendarObjectWithName(name), None
+ )
+ calendar.removeCalendarObjectWithName(name)
+ self.assertIdentical(
+ calendar.calendarObjectWithName(name), None
+ )
+
+
+ def test_removeCalendarObjectWithName_absent(self):
+ """
+ Attempt to remove an non-existing calendar object should raise.
+ """
+ calendar = self.calendarUnderTest()
+ self.assertRaises(
+ NoSuchObjectResourceError,
+ calendar.removeCalendarObjectWithName, "xyzzy"
+ )
+
+
+ def test_calendarName(self):
+ """
+ L{Calendar.name} reflects the name of the calendar.
+ """
+ self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
+
+
+ def test_calendarObjectName(self):
+ """
+ L{ICalendarObject.name} reflects the name of the calendar object.
+ """
+ self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
+
+
+ def test_component(self):
+ """
+ L{ICalendarObject.component} returns a L{VComponent} describing the
+ calendar data underlying that calendar object.
+ """
+ component = self.calendarObjectUnderTest().component()
+
+ self.failUnless(
+ isinstance(component, VComponent),
+ component
+ )
+
+ self.assertEquals(component.name(), "VCALENDAR")
+ self.assertEquals(component.mainType(), "VEVENT")
+ self.assertEquals(component.resourceUID(), "uid1")
+
+
+ def test_iCalendarText(self):
+ """
+ L{ICalendarObject.iCalendarText} returns a C{str} describing the same
+ data provided by L{ICalendarObject.component}.
+ """
+ text = self.calendarObjectUnderTest().iCalendarText()
+ self.assertIsInstance(text, str)
+ self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
+ self.assertIn("\r\nUID:uid1\r\n", text)
+ self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
+
+
+ def test_calendarObjectUID(self):
+ """
+ L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
+ of the calendar object's component.
+ """
+ self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
+
+
+ def test_organizer(self):
+ """
+ L{ICalendarObject.organizer} returns a C{str} describing the calendar
+ user address of the C{ORGANIZER} property of the calendar object's
+ component.
+ """
+ self.assertEquals(
+ self.calendarObjectUnderTest().organizer(),
+ "mailto:wsanchez at apple.com"
+ )
+
+
+ def test_calendarObjectWithUID_absent(self):
+ """
+ L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
+ don't exist.
+ """
+ calendar1 = self.calendarUnderTest()
+ self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
+
+
+ def test_calendars(self):
+ """
+ L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
+ providers, which are consistent with the results from
+ L{ICalendar.calendarWithName}.
+ """
+ # Add a dot directory to make sure we don't find it
+ # self.home1._path.child(".foo").createDirectory()
+ home = self.homeUnderTest()
+ calendars = list(home.calendars())
+
+ for calendar in calendars:
+ self.assertProvides(ICalendar, calendar)
+ self.assertEquals(calendar,
+ home.calendarWithName(calendar.name()))
+
+ self.assertEquals(
+ set(c.name() for c in calendars),
+ set(home1_calendarNames)
+ )
+
+
+ def test_calendarsAfterAddCalendar(self):
+ """
+ L{ICalendarHome.calendars} includes calendars recently added with
+ L{ICalendarHome.createCalendarWithName}.
+ """
+ home = self.homeUnderTest()
+ before = set(x.name() for x in home.calendars())
+ home.createCalendarWithName("new-name")
+ after = set(x.name() for x in home.calendars())
+ self.assertEquals(before | set(['new-name']), after)
+
+
+ def test_createCalendarObjectWithName_absent(self):
+ """
+ L{ICalendar.createCalendarObjectWithName} creates a new
+ L{ICalendarObject}.
+ """
+ calendar1 = self.calendarUnderTest()
+ name = "4.ics"
+ self.assertIdentical(calendar1.calendarObjectWithName(name), None)
+ component = VComponent.fromString(event4_text)
+ calendar1.createCalendarObjectWithName(name, component)
+
+ calendarObject = calendar1.calendarObjectWithName(name)
+ self.assertEquals(calendarObject.component(), component)
+
+ self.commit()
+
+ # Make sure notifications fire after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [
+ ("update", "home1"),
+ ("update", "home1/calendar_1"),
+ ]
+ )
+
+
+ def test_createCalendarObjectWithName_exists(self):
+ """
+ L{ICalendar.createCalendarObjectWithName} raises
+ L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
+ given name already exists in that calendar.
+ """
+ cal = self.calendarUnderTest()
+ comp = VComponent.fromString(event4_text)
+ self.assertRaises(
+ ObjectResourceNameAlreadyExistsError,
+ cal.createCalendarObjectWithName,
+ "1.ics", comp
+ )
+
+
+ def test_createCalendarObjectWithName_invalid(self):
+ """
+ L{ICalendar.createCalendarObjectWithName} raises
+ L{InvalidCalendarComponentError} if presented with invalid iCalendar
+ text.
+ """
+ self.assertRaises(
+ InvalidObjectResourceError,
+ self.calendarUnderTest().createCalendarObjectWithName,
+ "new", VComponent.fromString(event4notCalDAV_text)
+ )
+
+
+ def test_setComponent_invalid(self):
+ """
+ L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
+ presented with invalid iCalendar text.
+ """
+ calendarObject = self.calendarObjectUnderTest()
+ self.assertRaises(
+ InvalidObjectResourceError,
+ calendarObject.setComponent,
+ VComponent.fromString(event4notCalDAV_text)
+ )
+
+
+ def test_setComponent_uidchanged(self):
+ """
+ L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
+ when given a L{VComponent} whose UID does not match its existing UID.
+ """
+ calendar1 = self.calendarUnderTest()
+ component = VComponent.fromString(event4_text)
+ calendarObject = calendar1.calendarObjectWithName("1.ics")
+ self.assertRaises(
+ InvalidObjectResourceError,
+ calendarObject.setComponent, component
+ )
+
+
+ def test_calendarHomeWithUID_create(self):
+ """
+ L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
+ will create a calendar home that doesn't exist yet.
+ """
+ txn = self.transactionUnderTest()
+ noHomeUID = "xyzzy"
+ calendarHome = txn.calendarHomeWithUID(
+ noHomeUID,
+ create=True
+ )
+ def readOtherTxn():
+ otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
+ self.addCleanup(otherTxn.commit)
+ return otherTxn.calendarHomeWithUID(noHomeUID)
+ self.assertProvides(ICalendarHome, calendarHome)
+ # Default calendar should be automatically created.
+ self.assertProvides(ICalendar,
+ calendarHome.calendarWithName("calendar"))
+ # A concurrent transaction shouldn't be able to read it yet:
+ self.assertIdentical(readOtherTxn(), None)
+ self.commit()
+ # But once it's committed, other transactions should see it.
+ self.assertProvides(ICalendarHome, readOtherTxn())
+
+
+ def test_setComponent(self):
+ """
+ L{CalendarObject.setComponent} changes the result of
+ L{CalendarObject.component} within the same transaction.
+ """
+ component = VComponent.fromString(event1modified_text)
+
+ calendar1 = self.calendarUnderTest()
+ calendarObject = calendar1.calendarObjectWithName("1.ics")
+ oldComponent = calendarObject.component()
+ self.assertNotEqual(component, oldComponent)
+ calendarObject.setComponent(component)
+ self.assertEquals(calendarObject.component(), component)
+
+ # Also check a new instance
+ calendarObject = calendar1.calendarObjectWithName("1.ics")
+ self.assertEquals(calendarObject.component(), component)
+
+ self.commit()
+
+ # Make sure notification fired after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [
+ ("update", "home1"),
+ ("update", "home1/calendar_1"),
+ ]
+ )
+
+
+ def checkPropertiesMethod(self, thunk):
+ """
+ Verify that the given object has a properties method that returns an
+ L{IPropertyStore}.
+ """
+ properties = thunk.properties()
+ self.assertProvides(IPropertyStore, properties)
+
+
+ def test_homeProperties(self):
+ """
+ L{ICalendarHome.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.homeUnderTest())
+
+
+ def test_calendarProperties(self):
+ """
+ L{ICalendar.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.calendarUnderTest())
+
+
+ def test_calendarObjectProperties(self):
+ """
+ L{ICalendarObject.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.calendarObjectUnderTest())
+
+
+ def test_newCalendarObjectProperties(self):
+ """
+ L{ICalendarObject.properties} returns an empty property store for a
+ calendar object which has been created but not committed.
+ """
+ calendar = self.calendarUnderTest()
+ calendar.createCalendarObjectWithName(
+ "4.ics", VComponent.fromString(event4_text)
+ )
+ newEvent = calendar.calendarObjectWithName("4.ics")
+ self.assertEquals(newEvent.properties().items(), [])
+
+
+ def test_setComponentPreservesProperties(self):
+ """
+ L{ICalendarObject.setComponent} preserves properties.
+
+ (Some implementations must go to extra trouble to provide this
+ behavior; for example, file storage must copy extended attributes from
+ the existing file to the temporary file replacing it.)
+ """
+ propertyName = PropertyName("http://example.com/ns", "example")
+ propertyContent = WebDAVUnknownElement("sample content")
+ propertyContent.name = propertyName.name
+ propertyContent.namespace = propertyName.namespace
+
+ self.calendarObjectUnderTest().properties()[
+ propertyName] = propertyContent
+ self.commit()
+ # Sanity check; are properties even readable in a separate transaction?
+ # Should probably be a separate test.
+ self.assertEquals(
+ self.calendarObjectUnderTest().properties()[propertyName],
+ propertyContent)
+ obj = self.calendarObjectUnderTest()
+ event1_text = obj.iCalendarText()
+ event1_text_withDifferentSubject = event1_text.replace(
+ "SUMMARY:CalDAV protocol updates",
+ "SUMMARY:Changed"
+ )
+ # Sanity check; make sure the test has the right idea of the subject.
+ self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
+ newComponent = VComponent.fromString(event1_text_withDifferentSubject)
+ obj.setComponent(newComponent)
+
+ # Putting everything into a separate transaction to account for any
+ # caching that may take place.
+ self.commit()
+ self.assertEquals(
+ self.calendarObjectUnderTest().properties()[propertyName],
+ propertyContent
+ )
+
+
+ eventWithDropbox = "\r\n".join("""
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART;TZID=US/Eastern:20060101T100000
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:user01
+ATTENDEE;PARTSTAT=ACCEPTED:user01
+ATTACH;VALUE=URI:/calendars/users/home1/some-dropbox-id/some-dropbox-id/caldavd.plist
+X-APPLE-DROPBOX:/calendars/users/home1/dropbox/some-dropbox-id
+END:VEVENT
+END:VCALENDAR
+ """.strip().split("\n"))
+
+ def test_dropboxID(self):
+ """
+ L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
+ -APPLE-DROPBOX property, if available.
+ """
+ cal = self.calendarUnderTest()
+ cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
+ self.eventWithDropbox
+ )
+ )
+ obj = cal.calendarObjectWithName("drop.ics")
+ self.assertEquals(obj.dropboxID(), "some-dropbox-id")
+
+
+ def test_indexByDropboxProperty(self):
+ """
+ L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
+ object in the calendar home with the given final segment in its C{X
+ -APPLE-DROPBOX} property URI.
+ """
+ objName = "with-dropbox.ics"
+ cal = self.calendarUnderTest()
+ cal.createCalendarObjectWithName(
+ objName, VComponent.fromString(
+ self.eventWithDropbox
+ )
+ )
+ self.commit()
+ home = self.homeUnderTest()
+ cal = self.calendarUnderTest()
+ fromName = cal.calendarObjectWithName(objName)
+ fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
+ self.assertEquals(fromName, fromDropbox)
+
+
+ @inlineCallbacks
+ def createAttachmentTest(self, refresh):
+ """
+ Common logic for attachment-creation tests.
+ """
+ obj = self.calendarObjectUnderTest()
+ t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
+ t.write("new attachment")
+ t.write(" text")
+ t.loseConnection()
+ obj = refresh(obj)
+ class CaptureProtocol(Protocol):
+ buf = ''
+ def dataReceived(self, data):
+ self.buf += data
+ def connectionLost(self, reason):
+ self.deferred.callback(self.buf)
+ capture = CaptureProtocol()
+ capture.deferred = Deferred()
+ attachment = obj.attachmentWithName("new.attachment")
+ self.assertProvides(IAttachment, attachment)
+ attachment.retrieve(capture)
+ data = yield capture.deferred
+ self.assertEquals(data, "new attachment text")
+ contentType = attachment.contentType()
+ self.assertIsInstance(contentType, MimeType)
+ self.assertEquals(contentType, MimeType("text", "x-fixture"))
+ self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
+ self.assertEquals(
+ [attachment.name() for attachment in obj.attachments()],
+ ['new.attachment']
+ )
+
+
+ def test_createAttachment(self):
+ """
+ L{ICalendarObject.createAttachmentWithName} will store an
+ L{IAttachment} object that can be retrieved by
+ L{ICalendarObject.attachmentWithName}.
+ """
+ return self.createAttachmentTest(lambda x: x)
+
+
+ def test_createAttachmentCommit(self):
+ """
+ L{ICalendarObject.createAttachmentWithName} will store an
+ L{IAttachment} object that can be retrieved by
+ L{ICalendarObject.attachmentWithName} in subsequent transactions.
+ """
+ def refresh(obj):
+ self.commit()
+ return self.calendarObjectUnderTest()
+ return self.createAttachmentTest(refresh)
+
+
+ def test_removeAttachmentWithName(self, refresh=lambda x:x):
+ """
+ L{ICalendarObject.removeAttachmentWithName} will remove the calendar
+ object with the given name.
+ """
+ def deleteIt(ignored):
+ obj = self.calendarObjectUnderTest()
+ obj.removeAttachmentWithName("new.attachment")
+ obj = refresh(obj)
+ self.assertIdentical(
+ None, obj.attachmentWithName("new.attachment")
+ )
+ self.assertEquals(list(obj.attachments()), [])
+ return self.test_createAttachmentCommit().addCallback(deleteIt)
+
+
+ def test_removeAttachmentWithNameCommit(self):
+ """
+ L{ICalendarObject.removeAttachmentWithName} will remove the calendar
+ object with the given name. (After commit, it will still be gone.)
+ """
+ def refresh(obj):
+ self.commit()
+ return self.calendarObjectUnderTest()
+ return self.test_removeAttachmentWithName(refresh)
+
+
+ def test_noDropboxCalendar(self):
+ """
+ L{ICalendarObject.createAttachmentWithName} may create a directory
+ named 'dropbox', but this should not be seen as a calendar by
+ L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
+ """
+ obj = self.calendarObjectUnderTest()
+ t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
+ t.write("new attachment text")
+ t.loseConnection()
+ self.commit()
+ self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
+ None)
+ self.assertEquals(
+ set([n.name() for n in self.homeUnderTest().calendars()]),
+ set(home1_calendarNames))
+
+
+ def test_finishedOnCommit(self):
+ """
+ Calling L{ITransaction.abort} or L{ITransaction.commit} after
+ L{ITransaction.commit} has already been called raises an
+ L{AlreadyFinishedError}.
+ """
+ self.calendarObjectUnderTest()
+ txn = self.lastTransaction
+ self.commit()
+ self.assertRaises(AlreadyFinishedError, txn.commit)
+ self.assertRaises(AlreadyFinishedError, txn.abort)
+
+
+ def test_dontLeakCalendars(self):
+ """
+ Calendars in one user's calendar home should not show up in another
+ user's calendar home.
+ """
+ home2 = self.transactionUnderTest().calendarHomeWithUID(
+ "home2", create=True)
+ self.assertIdentical(home2.calendarWithName("calendar_1"), None)
+
+
+ def test_dontLeakObjects(self):
+ """
+ Calendar objects in one user's calendar should not show up in another
+ user's via uid or name queries.
+ """
+ home1 = self.homeUnderTest()
+ home2 = self.transactionUnderTest().calendarHomeWithUID(
+ "home2", create=True)
+ calendar1 = home1.calendarWithName("calendar_1")
+ calendar2 = home2.calendarWithName("calendar")
+ objects = list(home2.calendarWithName("calendar").calendarObjects())
+ self.assertEquals(objects, [])
+ for resourceName in self.requirements['home1']['calendar_1'].keys():
+ obj = calendar1.calendarObjectWithName(resourceName)
+ self.assertIdentical(
+ calendar2.calendarObjectWithName(resourceName), None)
+ self.assertIdentical(
+ calendar2.calendarObjectWithUID(obj.uid()), None)
+
+
+
+class StubNotifierFactory(object):
+
+ """ For testing push notifications without an XMPP server """
+
+ def __init__(self):
+ self.reset()
+
+ def newNotifier(self, label="default", id=None):
+ return Notifier(self, label=label, id=id)
+
+ def send(self, op, id):
+ self.history.append((op, id))
+
+ def reset(self):
+ self.history = []
Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_file.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -33,10 +33,10 @@
from txdav.common.icommondatastore import NoSuchHomeChildError
from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txcaldav.calendarstore.file import CalendarStore, CalendarHome
-from txcaldav.calendarstore.file import Calendar, CalendarObject
+from txdav.caldav.datastore.file import CalendarStore, CalendarHome
+from txdav.caldav.datastore.file import Calendar, CalendarObject
-from txcaldav.calendarstore.test.common import (
+from txdav.caldav.datastore.test.common import (
CommonTests, event4_text, event1modified_text)
storePath = FilePath(__file__).parent().child("calendar_store")
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_postgres.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,243 +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.
-##
-
-"""
-Tests for txcaldav.calendarstore.postgres, mostly based on
-L{txcaldav.calendarstore.test.common}.
-"""
-
-import gc
-
-from txcaldav.calendarstore.test.common import CommonTests as CalendarCommonTests
-from txcarddav.addressbookstore.test.common import CommonTests as AddressBookCommonTests
-from txdav.common.icommondatastore import NoSuchHomeChildError
-
-from twisted.trial import unittest
-from txdav.datastore.subpostgres import (PostgresService,
- DiagnosticConnectionWrapper)
-from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
-from twisted.internet.defer import Deferred, inlineCallbacks, succeed
-from twisted.internet import reactor
-from twext.python.filepath import CachingFilePath
-from twext.python.vcomponent import VComponent
-from twistedcaldav.vcard import Component as VCard
-from twisted.internet.task import deferLater
-from twisted.python import log
-
-
-
-def allInstancesOf(cls):
- for o in gc.get_referrers(cls):
- if isinstance(o, cls):
- yield o
-
-
-
-def dumpConnectionStatus():
- print '+++ ALL CONNECTIONS +++'
- for connection in allInstancesOf(DiagnosticConnectionWrapper):
- print connection.label, connection.state
- print '--- CONNECTIONS END ---'
-
-
-
-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 = PostgresStore(
- 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 CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
- """
- Calendar SQL storage tests.
- """
-
- @inlineCallbacks
- def setUp(self):
- super(CalendarSQLStorageTests, self).setUp()
- self.calendarStore = yield buildStore(self, self.notifierFactory)
- self.populate()
-
-
- def populate(self):
- populateTxn = self.calendarStore.newTransaction()
- for homeUID in self.requirements:
- calendars = self.requirements[homeUID]
- if calendars is not None:
- home = populateTxn.calendarHomeWithUID(homeUID, True)
- # We don't want the default calendar to appear unless it's
- # explicitly listed.
- try:
- home.removeCalendarWithName("calendar")
- except NoSuchHomeChildError:
- pass
- 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()
- self.notifierFactory.reset()
-
-
- def storeUnderTest(self):
- """
- Create and return a L{CalendarStore} for testing.
- """
- return self.calendarStore
-
-
-class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
- """
- AddressBook SQL storage tests.
- """
-
- @inlineCallbacks
- def setUp(self):
- super(AddressBookSQLStorageTests, self).setUp()
- self.addressbookStore = yield buildStore(self, self.notifierFactory)
- self.populate()
-
- def populate(self):
- populateTxn = self.addressbookStore.newTransaction()
- for homeUID in self.requirements:
- addressbooks = self.requirements[homeUID]
- if addressbooks is not None:
- home = populateTxn.addressbookHomeWithUID(homeUID, True)
- # We don't want the default addressbook to appear unless it's
- # explicitly listed.
- home.removeAddressBookWithName("addressbook")
- for addressbookName in addressbooks:
- addressbookObjNames = addressbooks[addressbookName]
- if addressbookObjNames is not None:
- home.createAddressBookWithName(addressbookName)
- addressbook = home.addressbookWithName(addressbookName)
- for objectName in addressbookObjNames:
- objData = addressbookObjNames[objectName]
- addressbook.createAddressBookObjectWithName(
- objectName, VCard.fromString(objData)
- )
-
- populateTxn.commit()
- self.notifierFactory.reset()
-
-
-
- def storeUnderTest(self):
- """
- Create and return a L{AddressBookStore} for testing.
- """
- return self.addressbookStore
-
Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_scheduling.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_scheduling.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -15,13 +15,13 @@
##
"""
-Tests for L{txcaldav.calendarstore.scheduling}.
+Tests for L{txdav.caldav.datastore.scheduling}.
"""
from twisted.trial.unittest import TestCase
-from txcaldav.calendarstore.test.common import CommonTests
-from txcaldav.calendarstore.test.test_file import setUpCalendarStore
-from txcaldav.calendarstore.scheduling import ImplicitStore
+from txdav.caldav.datastore.test.common import CommonTests
+from txdav.caldav.datastore.test.test_file import setUpCalendarStore
+from txdav.caldav.datastore.scheduling import ImplicitStore
simpleEvent = """BEGIN:VCALENDAR
VERSION:2.0
Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,78 @@
+##
+# 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 txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
+"""
+
+from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
+
+from txdav.common.datastore.test.util import SQLStoreBuilder
+from txdav.common.icommondatastore import NoSuchHomeChildError
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twext.python.vcomponent import VComponent
+
+
+theStoreBuilder = SQLStoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+class CalendarSQLStorageTests(CalendarCommonTests, unittest.TestCase):
+ """
+ Calendar SQL storage tests.
+ """
+
+ @inlineCallbacks
+ def setUp(self):
+ super(CalendarSQLStorageTests, self).setUp()
+ self.calendarStore = yield buildStore(self, self.notifierFactory)
+ self.populate()
+
+
+ def populate(self):
+ populateTxn = self.calendarStore.newTransaction()
+ for homeUID in self.requirements:
+ calendars = self.requirements[homeUID]
+ if calendars is not None:
+ home = populateTxn.calendarHomeWithUID(homeUID, True)
+ # We don't want the default calendar or inbox to appear unless it's
+ # explicitly listed.
+ try:
+ home.removeCalendarWithName("calendar")
+ home.removeCalendarWithName("inbox")
+ except NoSuchHomeChildError:
+ pass
+ 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()
+ self.notifierFactory.reset()
+
+
+ def storeUnderTest(self):
+ """
+ Create and return a L{CalendarStore} for testing.
+ """
+ return self.calendarStore
Deleted: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/util.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,305 +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.
-##
-"""
-Utility logic common to multiple backend implementations.
-"""
-
-from twext.python.vcomponent import InvalidICalendarDataError
-from twext.python.vcomponent import VComponent
-from twistedcaldav.vcard import Component as VCard
-from twistedcaldav.vcard import InvalidVCardDataError
-
-from txdav.common.icommondatastore import InvalidObjectResourceError,\
- NoSuchObjectResourceError
-
-
-def validateCalendarComponent(calendarObject, calendar, component):
- """
- Validate a calendar component for a particular calendar.
-
- @param calendarObject: The calendar object whose component will be replaced.
- @type calendarObject: L{ICalendarObject}
-
- @param calendar: The calendar which the L{ICalendarObject} is present in.
- @type calendar: L{ICalendar}
-
- @param component: The VComponent to be validated.
- @type component: L{VComponent}
- """
-
- if not isinstance(component, VComponent):
- raise TypeError(type(component))
-
- try:
- if component.resourceUID() != calendarObject.uid():
- raise InvalidObjectResourceError(
- "UID may not change (%s != %s)" % (
- component.resourceUID(), calendarObject.uid()
- )
- )
- except NoSuchObjectResourceError:
- pass
-
- try:
- # FIXME: This is a bad way to do this test, there should be a
- # Calendar-level API for it.
- if calendar.name() == 'inbox':
- component.validateComponentsForCalDAV(True)
- else:
- component.validateForCalDAV()
- except InvalidICalendarDataError, e:
- raise InvalidObjectResourceError(e)
-
-
-def dropboxIDFromCalendarObject(calendarObject):
- """
- Helper to implement L{ICalendarObject.dropboxID}.
-
- @param calendarObject: The calendar object to retrieve a dropbox ID for.
- @type calendarObject: L{ICalendarObject}
- """
- dropboxProperty = calendarObject.component(
- ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
- if dropboxProperty is not None:
- componentDropboxID = dropboxProperty.value().split("/")[-1]
- return componentDropboxID
- attachProperty = calendarObject.component().getFirstPropertyInAnyComponent("ATTACH")
- if attachProperty is not None:
- # Make sure the value type is URI
- valueType = attachProperty.params().get("VALUE", ("TEXT",))
- if valueType[0] == "URI":
- # FIXME: more aggressive checking to see if this URI is really the
- # 'right' URI. Maybe needs to happen in the front end.
- attachPath = attachProperty.value().split("/")[-2]
- return attachPath
-
- return calendarObject.uid() + ".dropbox"
-
-
-def validateAddressBookComponent(addressbookObject, vcard, component):
- """
- Validate an addressbook component for a particular addressbook.
-
- @param addressbookObject: The addressbook object whose component will be replaced.
- @type addressbookObject: L{IAddressBookObject}
-
- @param addressbook: The addressbook which the L{IAddressBookObject} is present in.
- @type addressbook: L{IAddressBook}
-
- @param component: The VComponent to be validated.
- @type component: L{VComponent}
- """
-
- if not isinstance(component, VCard):
- raise TypeError(type(component))
-
- try:
- if component.resourceUID() != addressbookObject.uid():
- raise InvalidObjectResourceError(
- "UID may not change (%s != %s)" % (
- component.resourceUID(), addressbookObject.uid()
- )
- )
- except NoSuchObjectResourceError:
- pass
-
- try:
- component.validForCardDAV()
- except InvalidVCardDataError, e:
- raise InvalidObjectResourceError(e)
-
-
-
-class CalendarSyncTokenHelper(object):
- """
- This is a mixin for use by data store implementations.
- """
-
- def syncToken(self):
- revision = self._txn.execSQL(
- "select REVISION from CALENDAR where RESOURCE_ID = %s",
- [self._resourceID])[0][0]
- return "%s#%s" % (self._resourceID, revision,)
-
- def _updateSyncToken(self):
-
- self._txn.execSQL("""
- update CALENDAR
- set (REVISION)
- = (nextval('CALENDAR_OBJECT_REVISION_SEQ'))
- where RESOURCE_ID = %s
- """,
- [self._resourceID]
- )
-
- def _insertRevision(self, name):
- self._changeRevision("insert", name)
-
- def _updateRevision(self, name):
- self._changeRevision("update", name)
-
- def _deleteRevision(self, name):
- self._changeRevision("delete", name)
-
- def _changeRevision(self, action, name):
-
- nextrevision = self._txn.execSQL("""
- select nextval('CALENDAR_OBJECT_REVISION_SEQ')
- """
- )
-
- if action == "delete":
- self._txn.execSQL("""
- update CALENDAR_OBJECT_REVISIONS
- set (REVISION, DELETED) = (%s, TRUE)
- where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [nextrevision, self._resourceID, name]
- )
- self._txn.execSQL("""
- update CALENDAR
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
- elif action == "update":
- self._txn.execSQL("""
- update CALENDAR_OBJECT_REVISIONS
- set (REVISION) = (%s)
- where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [nextrevision, self._resourceID, name]
- )
- self._txn.execSQL("""
- update CALENDAR
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
- elif action == "insert":
- self._txn.execSQL("""
- delete from CALENDAR_OBJECT_REVISIONS
- where CALENDAR_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [self._resourceID, name,]
- )
- self._txn.execSQL("""
- insert into CALENDAR_OBJECT_REVISIONS
- (CALENDAR_RESOURCE_ID, RESOURCE_NAME, REVISION, DELETED)
- values (%s, %s, %s, FALSE)
- """,
- [self._resourceID, name, nextrevision]
- )
- self._txn.execSQL("""
- update CALENDAR
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
-
-class AddressbookSyncTokenHelper(object):
- """
- This is a mixin for use by data store implementations.
- """
-
- def syncToken(self):
- revision = self._txn.execSQL(
- "select REVISION from ADDRESSBOOK where RESOURCE_ID = %s",
- [self._resourceID])[0][0]
- return "%s#%s" % (self._resourceID, revision,)
-
- def _updateSyncToken(self):
-
- self._txn.execSQL("""
- update ADDRESSBOOK
- set (REVISION)
- = (nextval('ADDRESSBOOK_OBJECT_REVISION_SEQ'))
- where RESOURCE_ID = %s
- """,
- [self._resourceID]
- )
-
- def _insertRevision(self, name):
- self._changeRevision("insert", name)
-
- def _updateRevision(self, name):
- self._changeRevision("update", name)
-
- def _deleteRevision(self, name):
- self._changeRevision("delete", name)
-
- def _changeRevision(self, action, name):
-
- nextrevision = self._txn.execSQL("""
- select nextval('ADDRESSBOOK_OBJECT_REVISION_SEQ')
- """
- )
-
- if action == "delete":
- self._txn.execSQL("""
- update ADDRESSBOOK_OBJECT_REVISIONS
- set (REVISION, DELETED) = (%s, TRUE)
- where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [nextrevision, self._resourceID, name]
- )
- self._txn.execSQL("""
- update ADDRESSBOOK
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
- elif action == "update":
- self._txn.execSQL("""
- update ADDRESSBOOK_OBJECT_REVISIONS
- set (REVISION) = (%s)
- where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [nextrevision, self._resourceID, name]
- )
- self._txn.execSQL("""
- update ADDRESSBOOK
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
- elif action == "insert":
- self._txn.execSQL("""
- delete from ADDRESSBOOK_OBJECT_REVISIONS
- where ADDRESSBOOK_RESOURCE_ID = %s and RESOURCE_NAME = %s
- """,
- [self._resourceID, name,]
- )
- self._txn.execSQL("""
- insert into ADDRESSBOOK_OBJECT_REVISIONS
- (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, REVISION, DELETED)
- values (%s, %s, %s, FALSE)
- """,
- [self._resourceID, name, nextrevision]
- )
- self._txn.execSQL("""
- update ADDRESSBOOK
- set (REVISION) = (%s)
- where RESOURCE_ID = %s
- """,
- [nextrevision, self._resourceID]
- )
-
\ No newline at end of file
Copied: CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/util.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/util.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,88 @@
+##
+# 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.
+##
+"""
+Utility logic common to multiple backend implementations.
+"""
+
+from twext.python.vcomponent import InvalidICalendarDataError
+from twext.python.vcomponent import VComponent
+
+from txdav.common.icommondatastore import InvalidObjectResourceError,\
+ NoSuchObjectResourceError
+
+
+def validateCalendarComponent(calendarObject, calendar, component, inserting):
+ """
+ Validate a calendar component for a particular calendar.
+
+ @param calendarObject: The calendar object whose component will be replaced.
+ @type calendarObject: L{ICalendarObject}
+
+ @param calendar: The calendar which the L{ICalendarObject} is present in.
+ @type calendar: L{ICalendar}
+
+ @param component: The VComponent to be validated.
+ @type component: L{VComponent}
+ """
+
+ if not isinstance(component, VComponent):
+ raise TypeError(type(component))
+
+ try:
+ if not inserting and component.resourceUID() != calendarObject.uid():
+ raise InvalidObjectResourceError(
+ "UID may not change (%s != %s)" % (
+ component.resourceUID(), calendarObject.uid()
+ )
+ )
+ except NoSuchObjectResourceError:
+ pass
+
+ try:
+ # FIXME: This is a bad way to do this test, there should be a
+ # Calendar-level API for it.
+ if calendar.name() == 'inbox':
+ component.validateComponentsForCalDAV(True)
+ else:
+ component.validateForCalDAV()
+ except InvalidICalendarDataError, e:
+ raise InvalidObjectResourceError(e)
+
+
+def dropboxIDFromCalendarObject(calendarObject):
+ """
+ Helper to implement L{ICalendarObject.dropboxID}.
+
+ @param calendarObject: The calendar object to retrieve a dropbox ID for.
+ @type calendarObject: L{ICalendarObject}
+ """
+ dropboxProperty = calendarObject.component(
+ ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
+ if dropboxProperty is not None:
+ componentDropboxID = dropboxProperty.value().split("/")[-1]
+ return componentDropboxID
+ attachProperty = calendarObject.component().getFirstPropertyInAnyComponent("ATTACH")
+ if attachProperty is not None:
+ # Make sure the value type is URI
+ valueType = attachProperty.params().get("VALUE", ("TEXT",))
+ if valueType[0] == "URI":
+ # FIXME: more aggressive checking to see if this URI is really the
+ # 'right' URI. Maybe needs to happen in the front end.
+ attachPath = attachProperty.value().split("/")[-2]
+ return attachPath
+
+ return calendarObject.uid() + ".dropbox"
+
\ No newline at end of file
Modified: CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/icalendarstore.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/caldav/icalendarstore.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore -*-
+# -*- test-case-name: txdav.caldav.datastore -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Deleted: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,322 +0,0 @@
-# -*- test-case-name: txcarddav.addressbookstore.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.
-##
-
-"""
-File addressbook store.
-"""
-
-__all__ = [
- "AddressBookStore",
- "AddressBookStoreTransaction",
- "AddressBookHome",
- "AddressBook",
- "AddressBookObject",
-]
-
-from errno import ENOENT
-
-from twext.web2.dav.element.rfc2518 import ResourceType
-
-from twistedcaldav.sharing import InvitesDatabase
-from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
-from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
-
-from txcarddav.iaddressbookstore import IAddressBook, IAddressBookObject
-from txcarddav.iaddressbookstore import IAddressBookHome
-
-from txdav.common.datastore.file import CommonDataStore, CommonHome,\
- CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
- CommonStubResource
-from txdav.common.icommondatastore import InvalidObjectResourceError,\
- NoSuchObjectResourceError, InternalDataStoreError
-from txdav.datastore.file import hidden, writeOperation
-from txdav.propertystore.base import PropertyName
-
-from twistedcaldav import customxml, carddavxml
-
-from zope.interface import implements
-
-AddressBookStore = CommonDataStore
-
-AddressBookStoreTransaction = CommonStoreTransaction
-
-class AddressBookHome(CommonHome):
-
- implements(IAddressBookHome)
-
- def __init__(self, uid, path, addressbookStore, transaction, notifier):
- super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
-
- self._childClass = AddressBook
-
- addressbooks = CommonHome.children
- listAddressbooks = CommonHome.listChildren
- addressbookWithName = CommonHome.childWithName
- createAddressBookWithName = CommonHome.createChildWithName
- removeAddressBookWithName = CommonHome.removeChildWithName
-
- @property
- def _addressbookStore(self):
- return self._dataStore
-
- def created(self):
- self.createAddressBookWithName("addressbook")
-
-class AddressBook(CommonHomeChild):
- """
- File-based implementation of L{IAddressBook}.
- """
- implements(IAddressBook)
-
- def __init__(self, name, addressbookHome, notifier, realName=None):
- """
- Initialize an addressbook pointing at a path on disk.
-
- @param name: the subdirectory of addressbookHome where this addressbook
- resides.
- @type name: C{str}
-
- @param addressbookHome: the home containing this addressbook.
- @type addressbookHome: L{AddressBookHome}
-
- @param realName: If this addressbook was just created, the name which it
- will eventually have on disk.
- @type realName: C{str}
- """
-
- super(AddressBook, self).__init__(name, addressbookHome, notifier,
- realName=realName)
-
- self._index = Index(self)
- self._invites = Invites(self)
- self._objectResourceClass = AddressBookObject
-
- @property
- def _addressbookHome(self):
- return self._home
-
- def resourceType(self):
- return ResourceType.addressbook
-
- ownerAddressBookHome = CommonHomeChild.ownerHome
- addressbookObjects = CommonHomeChild.objectResources
- listAddressbookObjects = CommonHomeChild.listObjectResources
- addressbookObjectWithName = CommonHomeChild.objectResourceWithName
- addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
- createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
- removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
- removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
- addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
-
-
- def initPropertyStore(self, props):
- # Setup peruser special properties
- props.setSpecialProperties(
- (
- PropertyName.fromElement(carddavxml.AddressBookDescription),
- ),
- (
- PropertyName.fromElement(customxml.GETCTag),
- ),
- )
-
- def _doValidate(self, component):
- component.validForCardDAV()
-
-
-class AddressBookObject(CommonObjectResource):
- """
- """
- implements(IAddressBookObject)
-
- def __init__(self, name, addressbook):
-
- super(AddressBookObject, self).__init__(name, addressbook)
-
-
- @property
- def _addressbook(self):
- return self._parentCollection
-
-
- def addressbook(self):
- return self._addressbook
-
-
- @writeOperation
- def setComponent(self, component):
- if not isinstance(component, VComponent):
- raise TypeError(type(component))
-
- try:
- if component.resourceUID() != self.uid():
- raise InvalidObjectResourceError(
- "UID may not change (%s != %s)" % (
- component.resourceUID(), self.uid()
- )
- )
- except NoSuchObjectResourceError:
- pass
-
- try:
- self._addressbook._doValidate(component)
- except InvalidVCardDataError, e:
- raise InvalidObjectResourceError(e)
-
- self._addressbook.retrieveOldIndex().addResource(
- self.name(), component
- )
-
- self._component = component
- # FIXME: needs to clear text cache
-
- def do():
- # Mark all properties as dirty, so they can be added back
- # to the newly updated file.
- self.properties().update(self.properties())
-
- backup = None
- if self._path.exists():
- backup = hidden(self._path.temporarySibling())
- self._path.moveTo(backup)
- fh = self._path.open("w")
- try:
- # FIXME: concurrency problem; if this write is interrupted
- # halfway through, the underlying file will be corrupt.
- fh.write(str(component))
- finally:
- fh.close()
-
- # Now re-write the original properties on the updated file
- self.properties().flush()
-
- def undo():
- if backup:
- backup.moveTo(self._path)
- else:
- self._path.remove()
- return undo
- self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
- if self._addressbook._notifier:
- self._transaction.postCommit(self._addressbook._notifier.notify)
-
-
-
- def component(self):
- if self._component is not None:
- return self._component
- text = self.text()
-
- try:
- component = VComponent.fromString(text)
- except InvalidVCardDataError, e:
- raise InternalDataStoreError(
- "File corruption detected (%s) in file: %s"
- % (e, self._path.path)
- )
- return component
-
-
- def text(self):
- if self._component is not None:
- return str(self._component)
- try:
- fh = self._path.open()
- except IOError, e:
- if e[0] == ENOENT:
- raise NoSuchObjectResourceError(self)
- else:
- raise
-
- try:
- text = fh.read()
- finally:
- fh.close()
-
- if not (
- text.startswith("BEGIN:VCARD\r\n") or
- text.endswith("\r\nEND:VCARD\r\n")
- ):
- raise InternalDataStoreError(
- "File corruption detected (improper start) in file: %s"
- % (self._path.path,)
- )
- return text
-
- vCardText = text
-
- def uid(self):
- if not hasattr(self, "_uid"):
- self._uid = self.component().resourceUID()
- return self._uid
-
-
-class AddressBookStubResource(CommonStubResource):
- """
- Just enough resource to keep the addressbook's sql DB classes going.
- """
-
- def isAddressBookCollection(self):
- return True
-
- def getChild(self, name):
- addressbookObject = self.resource.addressbookObjectWithName(name)
- if addressbookObject:
- class ChildResource(object):
- def __init__(self, addressbookObject):
- self.addressbookObject = addressbookObject
-
- def iAddressBook(self):
- return self.addressbookObject.component()
-
- return ChildResource(addressbookObject)
- else:
- return None
-
-
-class Index(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- def __init__(self, addressbook):
- self.addressbook = addressbook
- stubResource = AddressBookStubResource(addressbook)
- self._oldIndex = OldIndex(stubResource)
-
-
- def addressbookObjects(self):
- addressbook = self.addressbook
- for name, uid, componentType in self._oldIndex.bruteForceSearch():
- addressbookObject = addressbook.addressbookObjectWithName(name)
-
- # Precache what we found in the index
- addressbookObject._uid = uid
- addressbookObject._componentType = componentType
-
- yield addressbookObject
-
-
-class Invites(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- def __init__(self, addressbook):
- self.addressbook = addressbook
- stubResource = AddressBookStubResource(addressbook)
- self._oldInvites = InvitesDatabase(stubResource)
Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,306 @@
+# -*- test-case-name: txdav.carddav.datastore.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.
+##
+
+"""
+File addressbook store.
+"""
+
+__all__ = [
+ "AddressBookStore",
+ "AddressBookStoreTransaction",
+ "AddressBookHome",
+ "AddressBook",
+ "AddressBookObject",
+]
+
+from errno import ENOENT
+
+from twext.web2.dav.element.rfc2518 import ResourceType
+
+from twistedcaldav.sharing import InvitesDatabase
+from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
+from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
+
+from txdav.carddav.datastore.util import validateAddressBookComponent
+from txdav.carddav.iaddressbookstore import IAddressBook, IAddressBookObject
+from txdav.carddav.iaddressbookstore import IAddressBookHome
+
+from txdav.common.datastore.file import CommonDataStore, CommonHome,\
+ CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
+ CommonStubResource
+from txdav.common.icommondatastore import NoSuchObjectResourceError, InternalDataStoreError
+from txdav.base.datastore.file import hidden, writeOperation
+from txdav.base.propertystore.base import PropertyName
+
+from twistedcaldav import customxml, carddavxml
+
+from zope.interface import implements
+
+AddressBookStore = CommonDataStore
+
+AddressBookStoreTransaction = CommonStoreTransaction
+
+class AddressBookHome(CommonHome):
+
+ implements(IAddressBookHome)
+
+ def __init__(self, uid, path, addressbookStore, transaction, notifier):
+ super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
+
+ self._childClass = AddressBook
+
+ addressbooks = CommonHome.children
+ listAddressbooks = CommonHome.listChildren
+ addressbookWithName = CommonHome.childWithName
+ createAddressBookWithName = CommonHome.createChildWithName
+ removeAddressBookWithName = CommonHome.removeChildWithName
+
+ @property
+ def _addressbookStore(self):
+ return self._dataStore
+
+ def createdHome(self):
+ self.createAddressBookWithName("addressbook")
+
+class AddressBook(CommonHomeChild):
+ """
+ File-based implementation of L{IAddressBook}.
+ """
+ implements(IAddressBook)
+
+ def __init__(self, name, addressbookHome, notifier, realName=None):
+ """
+ Initialize an addressbook pointing at a path on disk.
+
+ @param name: the subdirectory of addressbookHome where this addressbook
+ resides.
+ @type name: C{str}
+
+ @param addressbookHome: the home containing this addressbook.
+ @type addressbookHome: L{AddressBookHome}
+
+ @param realName: If this addressbook was just created, the name which it
+ will eventually have on disk.
+ @type realName: C{str}
+ """
+
+ super(AddressBook, self).__init__(name, addressbookHome, notifier,
+ realName=realName)
+
+ self._index = Index(self)
+ self._invites = Invites(self)
+ self._objectResourceClass = AddressBookObject
+
+ @property
+ def _addressbookHome(self):
+ return self._home
+
+ def resourceType(self):
+ return ResourceType.addressbook #@UndefinedVariable
+
+ ownerAddressBookHome = CommonHomeChild.ownerHome
+ addressbookObjects = CommonHomeChild.objectResources
+ listAddressbookObjects = CommonHomeChild.listObjectResources
+ addressbookObjectWithName = CommonHomeChild.objectResourceWithName
+ addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
+ createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
+ removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
+ removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+ addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ PropertyName.fromElement(carddavxml.AddressBookDescription),
+ ),
+ (
+ PropertyName.fromElement(customxml.GETCTag),
+ ),
+ )
+
+ def _doValidate(self, component):
+ component.validForCardDAV()
+
+
+class AddressBookObject(CommonObjectResource):
+ """
+ """
+ implements(IAddressBookObject)
+
+ def __init__(self, name, addressbook):
+
+ super(AddressBookObject, self).__init__(name, addressbook)
+
+
+ @property
+ def _addressbook(self):
+ return self._parentCollection
+
+
+ def addressbook(self):
+ return self._addressbook
+
+
+ @writeOperation
+ def setComponent(self, component, inserting=False):
+ validateAddressBookComponent(self, self._addressbook, component, inserting)
+
+ self._addressbook.retrieveOldIndex().addResource(
+ self.name(), component
+ )
+
+ self._component = component
+ # FIXME: needs to clear text cache
+
+ def do():
+ # Mark all properties as dirty, so they can be added back
+ # to the newly updated file.
+ self.properties().update(self.properties())
+
+ backup = None
+ if self._path.exists():
+ backup = hidden(self._path.temporarySibling())
+ self._path.moveTo(backup)
+ fh = self._path.open("w")
+ try:
+ # FIXME: concurrency problem; if this write is interrupted
+ # halfway through, the underlying file will be corrupt.
+ fh.write(str(component))
+ finally:
+ fh.close()
+
+ # Now re-write the original properties on the updated file
+ self.properties().flush()
+
+ def undo():
+ if backup:
+ backup.moveTo(self._path)
+ else:
+ self._path.remove()
+ return undo
+ self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
+ if self._addressbook._notifier:
+ self._transaction.postCommit(self._addressbook._notifier.notify)
+
+
+
+ def component(self):
+ if self._component is not None:
+ return self._component
+ text = self.text()
+
+ try:
+ component = VComponent.fromString(text)
+ except InvalidVCardDataError, e:
+ raise InternalDataStoreError(
+ "File corruption detected (%s) in file: %s"
+ % (e, self._path.path)
+ )
+ return component
+
+
+ def text(self):
+ if self._component is not None:
+ return str(self._component)
+ try:
+ fh = self._path.open()
+ except IOError, e:
+ if e[0] == ENOENT:
+ raise NoSuchObjectResourceError(self)
+ else:
+ raise
+
+ try:
+ text = fh.read()
+ finally:
+ fh.close()
+
+ if not (
+ text.startswith("BEGIN:VCARD\r\n") or
+ text.endswith("\r\nEND:VCARD\r\n")
+ ):
+ raise InternalDataStoreError(
+ "File corruption detected (improper start) in file: %s"
+ % (self._path.path,)
+ )
+ return text
+
+ vCardText = text
+
+ def uid(self):
+ if not hasattr(self, "_uid"):
+ self._uid = self.component().resourceUID()
+ return self._uid
+
+
+class AddressBookStubResource(CommonStubResource):
+ """
+ Just enough resource to keep the addressbook's sql DB classes going.
+ """
+
+ def isAddressBookCollection(self):
+ return True
+
+ def getChild(self, name):
+ addressbookObject = self.resource.addressbookObjectWithName(name)
+ if addressbookObject:
+ class ChildResource(object):
+ def __init__(self, addressbookObject):
+ self.addressbookObject = addressbookObject
+
+ def iAddressBook(self):
+ return self.addressbookObject.component()
+
+ return ChildResource(addressbookObject)
+ else:
+ return None
+
+
+class Index(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, addressbook):
+ self.addressbook = addressbook
+ stubResource = AddressBookStubResource(addressbook)
+ self._oldIndex = OldIndex(stubResource)
+
+
+ def addressbookObjects(self):
+ addressbook = self.addressbook
+ for name, uid, componentType in self._oldIndex.bruteForceSearch():
+ addressbookObject = addressbook.addressbookObjectWithName(name)
+
+ # Precache what we found in the index
+ addressbookObject._uid = uid
+ addressbookObject._componentType = componentType
+
+ yield addressbookObject
+
+
+class Invites(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, addressbook):
+ self.addressbook = addressbook
+ stubResource = AddressBookStubResource(addressbook)
+ self._oldInvites = InvitesDatabase(stubResource)
Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,241 @@
+# -*- test-case-name: txdav.carddav.datastore.test.test_sql -*-
+##
+# 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.
+##
+
+__all__ = [
+ "AddressBookHome",
+ "AddressBook",
+ "AddressBookObject",
+]
+
+from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.http_headers import MimeType
+
+from twistedcaldav import carddavxml, customxml
+from twistedcaldav.vcard import Component as VCard
+
+from txdav.common.datastore.sql_legacy import \
+ PostgresLegacyABIndexEmulator, PostgresLegacyABInvitesEmulator,\
+ PostgresLegacyABSharesEmulator
+
+from txdav.carddav.datastore.util import validateAddressBookComponent
+from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook,\
+ IAddressBookObject
+
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
+ CommonObjectResource
+from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE,\
+ ADDRESSBOOK_BIND_TABLE, ADDRESSBOOK_OBJECT_REVISIONS_TABLE,\
+ ADDRESSBOOK_OBJECT_TABLE
+from txdav.base.propertystore.base import PropertyName
+
+from zope.interface.declarations import implements
+
+class AddressBookHome(CommonHome):
+
+ implements(IAddressBookHome)
+
+ def __init__(self, transaction, ownerUID, resourceID, notifier):
+ super(AddressBookHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+
+ self._shares = PostgresLegacyABSharesEmulator(self)
+ self._childClass = AddressBook
+ self._childTable = ADDRESSBOOK_TABLE
+ self._bindTable = ADDRESSBOOK_BIND_TABLE
+
+ addressbooks = CommonHome.children
+ listAddressbooks = CommonHome.listChildren
+ addressbookWithName = CommonHome.childWithName
+ createAddressBookWithName = CommonHome.createChildWithName
+ removeAddressBookWithName = CommonHome.removeChildWithName
+
+ def createdHome(self):
+ self.createAddressBookWithName("addressbook")
+
+class AddressBook(CommonHomeChild):
+ """
+ File-based implementation of L{IAddressBook}.
+ """
+ implements(IAddressBook)
+
+ def __init__(self, home, name, resourceID, notifier):
+ """
+ Initialize an addressbook pointing at a path on disk.
+
+ @param name: the subdirectory of addressbookHome where this addressbook
+ resides.
+ @type name: C{str}
+
+ @param addressbookHome: the home containing this addressbook.
+ @type addressbookHome: L{AddressBookHome}
+
+ @param realName: If this addressbook was just created, the name which it
+ will eventually have on disk.
+ @type realName: C{str}
+ """
+
+ super(AddressBook, self).__init__(home, name, resourceID, notifier)
+
+ self._index = PostgresLegacyABIndexEmulator(self)
+ self._invites = PostgresLegacyABInvitesEmulator(self)
+ self._objectResourceClass = AddressBookObject
+ self._bindTable = ADDRESSBOOK_BIND_TABLE
+ self._homeChildTable = ADDRESSBOOK_TABLE
+ self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+ self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+ @property
+ def _addressbookHome(self):
+ return self._home
+
+ def resourceType(self):
+ return ResourceType.addressbook #@UndefinedVariable
+
+ ownerAddressBookHome = CommonHomeChild.ownerHome
+ addressbookObjects = CommonHomeChild.objectResources
+ listAddressbookObjects = CommonHomeChild.listObjectResources
+ addressbookObjectWithName = CommonHomeChild.objectResourceWithName
+ addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
+ createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
+ removeAddressBookObjectWithName = CommonHomeChild.removeObjectResourceWithName
+ removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+ addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+
+ def initPropertyStore(self, props):
+ # Setup peruser special properties
+ props.setSpecialProperties(
+ (
+ PropertyName.fromElement(carddavxml.AddressBookDescription),
+ ),
+ (
+ PropertyName.fromElement(customxml.GETCTag),
+ ),
+ )
+
+ def _doValidate(self, component):
+ component.validForCardDAV()
+
+ def contentType(self):
+ """
+ The content type of Addresbook objects is text/vcard.
+ """
+ return MimeType.fromString("text/vcard; charset=utf-8")
+
+class AddressBookObject(CommonObjectResource):
+
+ implements(IAddressBookObject)
+
+ def __init__(self, name, addressbook, resid):
+
+ super(AddressBookObject, self).__init__(name, addressbook, resid)
+
+ self._objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+ @property
+ def _addressbook(self):
+ return self._parentCollection
+
+ def addressbook(self):
+ return self._addressbook
+
+ def setComponent(self, component, inserting=False):
+ validateAddressBookComponent(self, self._addressbook, component, inserting)
+
+ self.updateDatabase(component, inserting=inserting)
+ if inserting:
+ self._addressbook._insertRevision(self._name)
+ else:
+ self._addressbook._updateRevision(self._name)
+
+ if self._addressbook._notifier:
+ self._addressbook._home._txn.postCommit(self._addressbook._notifier.notify)
+
+ def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
+ """
+ Update the database tables for the new data being written.
+
+ @param component: addressbook data to store
+ @type component: L{Component}
+ """
+
+ componentText = str(component)
+ self._objectText = componentText
+
+ # ADDRESSBOOK_OBJECT table update
+ if inserting:
+ self._resourceID = self._txn.execSQL(
+ """
+ insert into ADDRESSBOOK_OBJECT
+ (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
+ values
+ (%s, %s, %s, %s)
+ returning RESOURCE_ID
+ """,
+ [
+ self._addressbook._resourceID,
+ self._name,
+ componentText,
+ component.resourceUID(),
+ ]
+ )[0][0]
+ else:
+ self._txn.execSQL(
+ """
+ update ADDRESSBOOK_OBJECT set
+ (VCARD_TEXT, VCARD_UID, MODIFIED)
+ =
+ (%s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+ where RESOURCE_ID = %s
+ """,
+ [
+ componentText,
+ component.resourceUID(),
+ self._resourceID
+ ]
+ )
+
+ def component(self):
+ return VCard.fromString(self.vCardText())
+
+ def text(self):
+ if self._objectText is None:
+ text = self._txn.execSQL(
+ "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
+ "RESOURCE_ID = %s", [self._resourceID]
+ )[0][0]
+ self._objectText = text
+ return text
+ else:
+ return self._objectText
+
+ vCardText = text
+
+ def uid(self):
+ return self.component().resourceUID()
+
+ def name(self):
+ return self._name
+
+ def componentType(self):
+ return self.component().mainType()
+
+ # IDataStoreResource
+ def contentType(self):
+ """
+ The content type of Addressbook objects is text/x-vcard.
+ """
+ return MimeType.fromString("text/vcard; charset=utf-8")
Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/__init__.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/__init__.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Deleted: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,897 +0,0 @@
-# -*- test-case-name: txcarddav.addressbookstore,txcaldav.calendarstore.test.test_postgres.AddressBookSQLStorageTests -*-
-##
-# 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 common addressbook store API functions.
-"""
-
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
- BrokenMethodImplementation, DoesNotImplement
-)
-
-from txdav.idav import IPropertyStore, IDataStore
-from txdav.propertystore.base import PropertyName
-
-from txdav.common.icommondatastore import (
- HomeChildNameAlreadyExistsError, ICommonTransaction
-)
-from txdav.common.icommondatastore import InvalidObjectResourceError
-from txdav.common.icommondatastore import NoSuchHomeChildError
-from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
-
-from txcarddav.iaddressbookstore import (
- IAddressBookObject, IAddressBookHome,
- IAddressBook, IAddressBookTransaction
-)
-from twistedcaldav.vcard import Component as VComponent
-from twistedcaldav.notify import Notifier
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2.dav import davxml
-from twext.web2.dav.element.base import WebDAVUnknownElement
-
-
-storePath = FilePath(__file__).parent().child("addressbook_store")
-
-homeRoot = storePath.child("ho").child("me").child("home1")
-
-adbk1Root = homeRoot.child("addressbook_1")
-
-addressbook1_objectNames = [
- "1.vcf",
- "2.vcf",
- "3.vcf",
-]
-
-
-home1_addressbookNames = [
- "addressbook_1",
- "addressbook_2",
- "addressbook_empty",
-]
-
-
-vcard4_text = (
- """BEGIN:VCARD
-VERSION:3.0
-N:Thompson;Default;;;
-FN:Default Thompson
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
-item1.X-ABADR:us
-UID:uid4
-END:VCARD
-""".replace("\n", "\r\n")
-)
-
-
-
-vcard4notCardDAV_text = ( # Missing UID, N and FN
-"""BEGIN:VCARD
-VERSION:3.0
-EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
-TEL;type=WORK;type=pref:1-555-555-5555
-TEL;type=CELL:1-444-444-4444
-item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
-item1.X-ABADR:us
-END:VCARD
-""".replace("\n", "\r\n")
-)
-
-
-
-vcard1modified_text = vcard4_text.replace(
- "\r\nUID:uid4\r\n",
- "\r\nUID:uid1\r\n"
-)
-
-
-def assertProvides(testCase, interface, provider):
- """
- Verify that C{provider} properly provides C{interface}
-
- @type interface: L{zope.interface.Interface}
- @type provider: C{provider}
- """
- try:
- verifyObject(interface, provider)
- except BrokenMethodImplementation, e:
- testCase.fail(e)
- except DoesNotImplement, e:
- testCase.fail("%r does not provide %s.%s" %
- (provider, interface.__module__, interface.getName()))
-
-
-
-class CommonTests(object):
- """
- Tests for common functionality of interfaces defined in
- L{txcarddav.iaddressbookstore}.
- """
-
- requirements = {
- "home1": {
- "addressbook_1": {
- "1.vcf": adbk1Root.child("1.vcf").getContent(),
- "2.vcf": adbk1Root.child("2.vcf").getContent(),
- "3.vcf": adbk1Root.child("3.vcf").getContent()
- },
- "addressbook_2": {},
- "addressbook_empty": {},
- "not_a_addressbook": None
- },
- "not_a_home": None
- }
-
- def storeUnderTest(self):
- """
- Subclasses must override this to return an L{IAddressBookStore}
- provider which adheres to the structure detailed by
- L{CommonTests.requirements}. This attribute is a dict of dict of dicts;
- the outermost layer representing UIDs mapping to addressbook homes,
- then addressbook names mapping to addressbook collections, and finally
- addressbook object names mapping to addressbook object text.
- """
- raise NotImplementedError()
-
-
- lastTransaction = None
- savedStore = None
-
- def transactionUnderTest(self):
- """
- Create a transaction from C{storeUnderTest} and save it as
- C[lastTransaction}. Also makes sure to use the same store, saving the
- value from C{storeUnderTest}.
- """
- if self.lastTransaction is not None:
- return self.lastTransaction
- if self.savedStore is None:
- self.savedStore = self.storeUnderTest()
- txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
- return txn
-
-
- def commit(self):
- """
- Commit the last transaction created from C{transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.commit()
- self.lastTransaction = None
-
-
- def abort(self):
- """
- Abort the last transaction created from C[transactionUnderTest}, and
- clear it.
- """
- self.lastTransaction.abort()
- self.lastTransaction = None
-
- def setUp(self):
- self.notifierFactory = StubNotifierFactory()
-
- def tearDown(self):
- if self.lastTransaction is not None:
- self.commit()
-
-
-
- def homeUnderTest(self):
- """
- Get the addressbook home detailed by C{requirements['home1']}.
- """
- return self.transactionUnderTest().addressbookHomeWithUID("home1")
-
-
- def addressbookUnderTest(self):
- """
- Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
- """
- return self.homeUnderTest().addressbookWithName("addressbook_1")
-
-
- def addressbookObjectUnderTest(self):
- """
- Get the addressbook detailed by
- C{requirements['home1']['addressbook_1']['1.vcf']}.
- """
- return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
-
-
- assertProvides = assertProvides
-
-
- def test_addressbookStoreProvides(self):
- """
- The addressbook store provides L{IAddressBookStore} and its required
- attributes.
- """
- addressbookStore = self.storeUnderTest()
- self.assertProvides(IDataStore, addressbookStore)
-
-
- def test_transactionProvides(self):
- """
- The transactions generated by the addressbook store provide
- L{IAddressBookStoreTransaction} and its required attributes.
- """
- txn = self.transactionUnderTest()
- self.assertProvides(ICommonTransaction, txn)
- self.assertProvides(IAddressBookTransaction, txn)
-
-
- def test_homeProvides(self):
- """
- The addressbook homes generated by the addressbook store provide
- L{IAddressBookHome} and its required attributes.
- """
- self.assertProvides(IAddressBookHome, self.homeUnderTest())
-
-
- def test_addressbookProvides(self):
- """
- The addressbooks generated by the addressbook store provide L{IAddressBook} and
- its required attributes.
- """
- self.assertProvides(IAddressBook, self.addressbookUnderTest())
-
-
- def test_addressbookObjectProvides(self):
- """
- The addressbook objects generated by the addressbook store provide
- L{IAddressBookObject} and its required attributes.
- """
- self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
-
- def test_notifierID(self):
- home = self.homeUnderTest()
- self.assertEquals(home.notifierID(), "home1")
- addressbook = home.addressbookWithName("addressbook_1")
- self.assertEquals(addressbook.notifierID(), "home1")
- self.assertEquals(addressbook.notifierID(label="collection"), "home1/addressbook_1")
-
-
- def test_addressbookHomeWithUID_exists(self):
- """
- Finding an existing addressbook home by UID results in an object that
- provides L{IAddressBookHome} and has a C{uid()} method that returns the
- same value that was passed in.
- """
- addressbookHome = (self.transactionUnderTest()
- .addressbookHomeWithUID("home1"))
- self.assertEquals(addressbookHome.uid(), "home1")
- self.assertProvides(IAddressBookHome, addressbookHome)
-
-
- def test_addressbookHomeWithUID_absent(self):
- """
- L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
- when asked for a non-existent addressbook home.
- """
- txn = self.transactionUnderTest()
- self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
-
-
- def test_addressbookWithName_exists(self):
- """
- L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
- whose name matches the one passed in.
- """
- home = self.homeUnderTest()
- for name in home1_addressbookNames:
- addressbook = home.addressbookWithName(name)
- if addressbook is None:
- self.fail("addressbook %r didn't exist" % (name,))
- self.assertProvides(IAddressBook, addressbook)
- self.assertEquals(addressbook.name(), name)
-
-
- def test_addressbookRename(self):
- """
- L{IAddressBook.rename} changes the name of the L{IAddressBook}.
- """
- home = self.homeUnderTest()
- addressbook = home.addressbookWithName("addressbook_1")
- addressbook.rename("some_other_name")
- def positiveAssertions():
- self.assertEquals(addressbook.name(), "some_other_name")
- self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
- self.assertEquals(None, home.addressbookWithName("addressbook_1"))
- positiveAssertions()
- self.commit()
- home = self.homeUnderTest()
- addressbook = home.addressbookWithName("some_other_name")
- positiveAssertions()
- # FIXME: revert
- # FIXME: test for multiple renames
- # FIXME: test for conflicting renames (a->b, c->a in the same txn)
-
-
- def test_addressbookWithName_absent(self):
- """
- L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
- do not exist.
- """
- self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
- None)
-
-
- def test_createAddressBookWithName_absent(self):
- """
- L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
- can be retrieved with L{IAddressBookHome.addressbookWithName}.
- """
- home = self.homeUnderTest()
- name = "new"
- self.assertIdentical(home.addressbookWithName(name), None)
- home.createAddressBookWithName(name)
- self.assertNotIdentical(home.addressbookWithName(name), None)
- def checkProperties():
- addressbookProperties = home.addressbookWithName(name).properties()
- self.assertEquals(
- addressbookProperties[
- PropertyName.fromString(davxml.ResourceType.sname())
- ],
- davxml.ResourceType.addressbook
- ) #@UndefinedVariable
- checkProperties()
- self.commit()
-
- # Make sure notification fired after commit
- 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.addressbookWithName(name), None)
-
- # FIXME: These two lines aren't in the calendar common tests:
- # home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
- # "home1")
-
- # Sanity check: are the properties actually persisted?
- # FIXME: no independent testing of this right now
- checkProperties()
-
-
- def test_createAddressBookWithName_exists(self):
- """
- L{IAddressBookHome.createAddressBookWithName} raises
- L{AddressBookAlreadyExistsError} when the name conflicts with an already-
- existing address book.
- """
- for name in home1_addressbookNames:
- self.assertRaises(
- HomeChildNameAlreadyExistsError,
- self.homeUnderTest().createAddressBookWithName, name
- )
-
-
- def test_removeAddressBookWithName_exists(self):
- """
- L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
- exists.
- """
- home = self.homeUnderTest()
- # FIXME: test transactions
- for name in home1_addressbookNames:
- self.assertNotIdentical(home.addressbookWithName(name), None)
- home.removeAddressBookWithName(name)
- self.assertEquals(home.addressbookWithName(name), None)
-
- self.commit()
-
- # Make sure notification fired after commit
- self.assertEquals(
- self.notifierFactory.history,
- [("update", "home1"), ("update", "home1"), ("update", "home1")]
- )
-
-
- def test_removeAddressBookWithName_absent(self):
- """
- Attempt to remove an non-existing addressbook should raise.
- """
- home = self.homeUnderTest()
- self.assertRaises(NoSuchHomeChildError,
- home.removeAddressBookWithName, "xyzzy")
-
-
- def test_addressbookObjects(self):
- """
- L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
- in the filesystem, in name order, but skip those with hidden names.
- """
- addressbook1 = self.addressbookUnderTest()
- addressbookObjects = list(addressbook1.addressbookObjects())
-
- for addressbookObject in addressbookObjects:
- self.assertProvides(IAddressBookObject, addressbookObject)
- self.assertEquals(
- addressbook1.addressbookObjectWithName(addressbookObject.name()),
- addressbookObject
- )
-
- self.assertEquals(
- set(o.name() for o in addressbookObjects),
- set(addressbook1_objectNames)
- )
-
-
- def test_addressbookObjectsWithRemovedObject(self):
- """
- L{IAddressBook.addressbookObjects} skips those objects which have been
- removed by L{AddressBook.removeAddressBookObjectWithName} in the same
- transaction, even if it has not yet been committed.
- """
- addressbook1 = self.addressbookUnderTest()
- addressbook1.removeAddressBookObjectWithName("2.vcf")
- addressbookObjects = list(addressbook1.addressbookObjects())
- self.assertEquals(set(o.name() for o in addressbookObjects),
- set(addressbook1_objectNames) - set(["2.vcf"]))
-
-
- def test_ownerAddressBookHome(self):
- """
- L{IAddressBook.ownerAddressBookHome} should match the home UID.
- """
- self.assertEquals(
- self.addressbookUnderTest().ownerAddressBookHome().uid(),
- self.homeUnderTest().uid()
- )
-
-
- def test_addressbookObjectWithName_exists(self):
- """
- L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
- provider for addressbooks which already exist.
- """
- addressbook1 = self.addressbookUnderTest()
- for name in addressbook1_objectNames:
- addressbookObject = addressbook1.addressbookObjectWithName(name)
- self.assertProvides(IAddressBookObject, addressbookObject)
- self.assertEquals(addressbookObject.name(), name)
- # FIXME: add more tests based on CommonTests.requirements
-
-
- def test_addressbookObjectWithName_absent(self):
- """
- L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
- don't exist.
- """
- addressbook1 = self.addressbookUnderTest()
- self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
-
-
- def test_removeAddressBookObjectWithUID_exists(self):
- """
- Remove an existing addressbook object.
- """
- addressbook = self.addressbookUnderTest()
- for name in addressbook1_objectNames:
- uid = (u'uid' + name.rstrip(".vcf"))
- self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
- None)
- addressbook.removeAddressBookObjectWithUID(uid)
- self.assertEquals(
- addressbook.addressbookObjectWithUID(uid),
- None
- )
- self.assertEquals(
- addressbook.addressbookObjectWithName(name),
- None
- )
-
-
- def test_removeAddressBookObjectWithName_exists(self):
- """
- Remove an existing addressbook object.
- """
- addressbook = self.addressbookUnderTest()
- for name in addressbook1_objectNames:
- self.assertNotIdentical(
- addressbook.addressbookObjectWithName(name), None
- )
- addressbook.removeAddressBookObjectWithName(name)
- self.assertIdentical(
- addressbook.addressbookObjectWithName(name), None
- )
-
- # Make sure notifications are fired after commit
- self.commit()
- self.assertEquals(
- self.notifierFactory.history,
- [
- ("update", "home1"),
- ("update", "home1/addressbook_1"),
- ("update", "home1"),
- ("update", "home1/addressbook_1"),
- ("update", "home1"),
- ("update", "home1/addressbook_1"),
- ]
- )
-
-
- def test_removeAddressBookObjectWithName_absent(self):
- """
- Attempt to remove an non-existing addressbook object should raise.
- """
- addressbook = self.addressbookUnderTest()
- self.assertRaises(
- NoSuchObjectResourceError,
- addressbook.removeAddressBookObjectWithName, "xyzzy"
- )
-
-
- def test_addressbookName(self):
- """
- L{AddressBook.name} reflects the name of the addressbook.
- """
- self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
-
-
- def test_addressbookObjectName(self):
- """
- L{IAddressBookObject.name} reflects the name of the addressbook object.
- """
- self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
-
-
- def test_component(self):
- """
- L{IAddressBookObject.component} returns a L{VComponent} describing the
- addressbook data underlying that addressbook object.
- """
- component = self.addressbookObjectUnderTest().component()
-
- self.failUnless(
- isinstance(component, VComponent),
- component
- )
-
- self.assertEquals(component.name(), "VCARD")
- self.assertEquals(component.resourceUID(), "uid1")
-
-
- def test_iAddressBookText(self):
- """
- L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
- data provided by L{IAddressBookObject.component}.
- """
- text = self.addressbookObjectUnderTest().vCardText()
- self.assertIsInstance(text, str)
- self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
- self.assertIn("\r\nUID:uid1\r\n", text)
- self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
-
-
- def test_addressbookObjectUID(self):
- """
- L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
- of the addressbook object's component.
- """
- self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
-
-
- def test_addressbookObjectWithUID_absent(self):
- """
- L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
- don't exist.
- """
- addressbook1 = self.addressbookUnderTest()
- self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
-
-
- def test_addressbooks(self):
- """
- L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
- providers, which are consistent with the results from
- L{IAddressBook.addressbookWithName}.
- """
- # Add a dot directory to make sure we don't find it
- # self.home1._path.child(".foo").createDirectory()
- home = self.homeUnderTest()
- addressbooks = list(home.addressbooks())
-
- for addressbook in addressbooks:
- self.assertProvides(IAddressBook, addressbook)
- self.assertEquals(addressbook,
- home.addressbookWithName(addressbook.name()))
-
- self.assertEquals(
- set(c.name() for c in addressbooks),
- set(home1_addressbookNames)
- )
-
-
- def test_addressbooksAfterAddAddressBook(self):
- """
- L{IAddressBookHome.addressbooks} includes addressbooks recently added with
- L{IAddressBookHome.createAddressBookWithName}.
- """
- home = self.homeUnderTest()
- before = set(x.name() for x in home.addressbooks())
- home.createAddressBookWithName("new-name")
- after = set(x.name() for x in home.addressbooks())
- self.assertEquals(before | set(['new-name']), after)
-
-
- def test_createAddressBookObjectWithName_absent(self):
- """
- L{IAddressBook.createAddressBookObjectWithName} creates a new
- L{IAddressBookObject}.
- """
- addressbook1 = self.addressbookUnderTest()
- name = "4.vcf"
- self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
- component = VComponent.fromString(vcard4_text)
- addressbook1.createAddressBookObjectWithName(name, component)
-
- addressbookObject = addressbook1.addressbookObjectWithName(name)
- self.assertEquals(addressbookObject.component(), component)
-
- self.commit()
-
- # Make sure notifications fire after commit
- self.assertEquals(
- self.notifierFactory.history,
- [
- ("update", "home1"),
- ("update", "home1/addressbook_1"),
- ]
- )
-
-
- def test_createAddressBookObjectWithName_exists(self):
- """
- L{IAddressBook.createAddressBookObjectWithName} raises
- L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
- given name already exists in that addressbook.
- """
- self.assertRaises(
- ObjectResourceNameAlreadyExistsError,
- self.addressbookUnderTest().createAddressBookObjectWithName,
- "1.vcf", VComponent.fromString(vcard4_text)
- )
-
-
- def test_createAddressBookObjectWithName_invalid(self):
- """
- L{IAddressBook.createAddressBookObjectWithName} raises
- L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
- text.
- """
- self.assertRaises(
- InvalidObjectResourceError,
- self.addressbookUnderTest().createAddressBookObjectWithName,
- "new", VComponent.fromString(vcard4notCardDAV_text)
- )
-
-
- def test_setComponent_invalid(self):
- """
- L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
- presented with invalid iAddressBook text.
- """
- addressbookObject = self.addressbookObjectUnderTest()
- self.assertRaises(
- InvalidObjectResourceError,
- addressbookObject.setComponent,
- VComponent.fromString(vcard4notCardDAV_text)
- )
-
-
- def test_setComponent_uidchanged(self):
- """
- L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
- when given a L{VComponent} whose UID does not match its existing UID.
- """
- addressbook1 = self.addressbookUnderTest()
- component = VComponent.fromString(vcard4_text)
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- self.assertRaises(
- InvalidObjectResourceError,
- addressbookObject.setComponent, component
- )
-
-
- def test_addressbookHomeWithUID_create(self):
- """
- L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
- will create a addressbook home that doesn't exist yet.
- """
- txn = self.transactionUnderTest()
- noHomeUID = "xyzzy"
- addressbookHome = txn.addressbookHomeWithUID(
- noHomeUID,
- create=True
- )
- def readOtherTxn():
- otherTxn = self.savedStore.newTransaction()
- self.addCleanup(otherTxn.commit)
- return otherTxn.addressbookHomeWithUID(noHomeUID)
- self.assertProvides(IAddressBookHome, addressbookHome)
- # A concurrent transaction shouldn't be able to read it yet:
- self.assertIdentical(readOtherTxn(), None)
- self.commit()
- # But once it's committed, other transactions should see it.
- self.assertProvides(IAddressBookHome, readOtherTxn())
-
-
- def test_setComponent(self):
- """
- L{AddressBookObject.setComponent} changes the result of
- L{AddressBookObject.component} within the same transaction.
- """
- component = VComponent.fromString(vcard1modified_text)
-
- addressbook1 = self.addressbookUnderTest()
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- oldComponent = addressbookObject.component()
- self.assertNotEqual(component, oldComponent)
- addressbookObject.setComponent(component)
- self.assertEquals(addressbookObject.component(), component)
-
- # Also check a new instance
- addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
- self.assertEquals(addressbookObject.component(), component)
-
- self.commit()
-
- # Make sure notification fired after commit
- self.assertEquals(
- self.notifierFactory.history,
- [
- ("update", "home1"),
- ("update", "home1/addressbook_1"),
- ]
- )
-
- def checkPropertiesMethod(self, thunk):
- """
- Verify that the given object has a properties method that returns an
- L{IPropertyStore}.
- """
- properties = thunk.properties()
- self.assertProvides(IPropertyStore, properties)
-
-
- def test_homeProperties(self):
- """
- L{IAddressBookHome.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.homeUnderTest())
-
-
- def test_addressbookProperties(self):
- """
- L{IAddressBook.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.addressbookUnderTest())
-
-
- def test_addressbookObjectProperties(self):
- """
- L{IAddressBookObject.properties} returns a property store.
- """
- self.checkPropertiesMethod(self.addressbookObjectUnderTest())
-
-
- def test_newAddressBookObjectProperties(self):
- """
- L{IAddressBookObject.properties} returns an empty property store for a
- addressbook object which has been created but not committed.
- """
- addressbook = self.addressbookUnderTest()
- addressbook.createAddressBookObjectWithName(
- "4.vcf", VComponent.fromString(vcard4_text)
- )
- newEvent = addressbook.addressbookObjectWithName("4.vcf")
- self.assertEquals(newEvent.properties().items(), [])
-
-
- def test_setComponentPreservesProperties(self):
- """
- L{IAddressBookObject.setComponent} preserves properties.
-
- (Some implementations must go to extra trouble to provide this
- behavior; for example, file storage must copy extended attributes from
- the existing file to the temporary file replacing it.)
- """
- propertyName = PropertyName("http://example.com/ns", "example")
- propertyContent = WebDAVUnknownElement("sample content")
- propertyContent.name = propertyName.name
- propertyContent.namespace = propertyName.namespace
-
- self.addressbookObjectUnderTest().properties()[
- propertyName] = propertyContent
- self.commit()
- # Sanity check; are properties even readable in a separate transaction?
- # Should probably be a separate test.
- self.assertEquals(
- self.addressbookObjectUnderTest().properties()[propertyName],
- propertyContent)
- obj = self.addressbookObjectUnderTest()
- vcard1_text = obj.vCardText()
- vcard1_text_withDifferentNote = vcard1_text.replace(
- "NOTE:CardDAV protocol updates",
- "NOTE:Changed"
- )
- # Sanity check; make sure the test has the right idea of the subject.
- self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
- newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
- obj.setComponent(newComponent)
-
- # Putting everything into a separate transaction to account for any
- # caching that may take place.
- self.commit()
- self.assertEquals(
- self.addressbookObjectUnderTest().properties()[propertyName],
- propertyContent
- )
-
-
- def test_dontLeakAddressbooks(self):
- """
- Addressbooks in one user's addressbook home should not show up in another
- user's addressbook home.
- """
- home2 = self.transactionUnderTest().addressbookHomeWithUID(
- "home2", create=True)
- self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
-
-
- def test_dontLeakObjects(self):
- """
- Addressbook objects in one user's addressbook should not show up in another
- user's via uid or name queries.
- """
- home1 = self.homeUnderTest()
- home2 = self.transactionUnderTest().addressbookHomeWithUID(
- "home2", create=True)
- addressbook1 = home1.addressbookWithName("addressbook_1")
- addressbook2 = home2.addressbookWithName("addressbook")
- objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
- self.assertEquals(objects, [])
- for resourceName in self.requirements['home1']['addressbook_1'].keys():
- obj = addressbook1.addressbookObjectWithName(resourceName)
- self.assertIdentical(
- addressbook2.addressbookObjectWithName(resourceName), None)
- self.assertIdentical(
- addressbook2.addressbookObjectWithUID(obj.uid()), None)
-
-
-
-class StubNotifierFactory(object):
-
- """ For testing push notifications without an XMPP server """
-
- def __init__(self):
- self.reset()
-
- def newNotifier(self, label="default", id=None):
- return Notifier(self, label=label, id=id)
-
- def send(self, op, id):
- self.history.append((op, id))
-
- def reset(self):
- self.history = []
Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py (from rev 6185, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/common.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/common.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,897 @@
+# -*- test-case-name: txdav.carddav.datastore,txdav.caldav.datastore.test.test_postgres.AddressBookSQLStorageTests -*-
+##
+# 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 common addressbook store API functions.
+"""
+
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import (
+ BrokenMethodImplementation, DoesNotImplement
+)
+
+from txdav.idav import IPropertyStore, IDataStore
+from txdav.base.propertystore.base import PropertyName
+
+from txdav.common.icommondatastore import (
+ HomeChildNameAlreadyExistsError, ICommonTransaction
+)
+from txdav.common.icommondatastore import InvalidObjectResourceError
+from txdav.common.icommondatastore import NoSuchHomeChildError
+from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
+
+from txdav.carddav.iaddressbookstore import (
+ IAddressBookObject, IAddressBookHome,
+ IAddressBook, IAddressBookTransaction
+)
+from twistedcaldav.vcard import Component as VComponent
+from twistedcaldav.notify import Notifier
+
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import WebDAVUnknownElement
+
+
+storePath = FilePath(__file__).parent().child("addressbook_store")
+
+homeRoot = storePath.child("ho").child("me").child("home1")
+
+adbk1Root = homeRoot.child("addressbook_1")
+
+addressbook1_objectNames = [
+ "1.vcf",
+ "2.vcf",
+ "3.vcf",
+]
+
+
+home1_addressbookNames = [
+ "addressbook_1",
+ "addressbook_2",
+ "addressbook_empty",
+]
+
+
+vcard4_text = (
+ """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
+item1.X-ABADR:us
+UID:uid4
+END:VCARD
+""".replace("\n", "\r\n")
+)
+
+
+
+vcard4notCardDAV_text = ( # Missing UID, N and FN
+"""BEGIN:VCARD
+VERSION:3.0
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
+item1.X-ABADR:us
+END:VCARD
+""".replace("\n", "\r\n")
+)
+
+
+
+vcard1modified_text = vcard4_text.replace(
+ "\r\nUID:uid4\r\n",
+ "\r\nUID:uid1\r\n"
+)
+
+
+def assertProvides(testCase, interface, provider):
+ """
+ Verify that C{provider} properly provides C{interface}
+
+ @type interface: L{zope.interface.Interface}
+ @type provider: C{provider}
+ """
+ try:
+ verifyObject(interface, provider)
+ except BrokenMethodImplementation, e:
+ testCase.fail(e)
+ except DoesNotImplement, e:
+ testCase.fail("%r does not provide %s.%s" %
+ (provider, interface.__module__, interface.getName()))
+
+
+
+class CommonTests(object):
+ """
+ Tests for common functionality of interfaces defined in
+ L{txdav.carddav.iaddressbookstore}.
+ """
+
+ requirements = {
+ "home1": {
+ "addressbook_1": {
+ "1.vcf": adbk1Root.child("1.vcf").getContent(),
+ "2.vcf": adbk1Root.child("2.vcf").getContent(),
+ "3.vcf": adbk1Root.child("3.vcf").getContent()
+ },
+ "addressbook_2": {},
+ "addressbook_empty": {},
+ "not_a_addressbook": None
+ },
+ "not_a_home": None
+ }
+
+ def storeUnderTest(self):
+ """
+ Subclasses must override this to return an L{IAddressBookStore}
+ provider which adheres to the structure detailed by
+ L{CommonTests.requirements}. This attribute is a dict of dict of dicts;
+ the outermost layer representing UIDs mapping to addressbook homes,
+ then addressbook names mapping to addressbook collections, and finally
+ addressbook object names mapping to addressbook object text.
+ """
+ raise NotImplementedError()
+
+
+ lastTransaction = None
+ savedStore = None
+
+ def transactionUnderTest(self):
+ """
+ Create a transaction from C{storeUnderTest} and save it as
+ C[lastTransaction}. Also makes sure to use the same store, saving the
+ value from C{storeUnderTest}.
+ """
+ if self.lastTransaction is not None:
+ return self.lastTransaction
+ if self.savedStore is None:
+ self.savedStore = self.storeUnderTest()
+ txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
+ return txn
+
+
+ def commit(self):
+ """
+ Commit the last transaction created from C{transactionUnderTest}, and
+ clear it.
+ """
+ self.lastTransaction.commit()
+ self.lastTransaction = None
+
+
+ def abort(self):
+ """
+ Abort the last transaction created from C[transactionUnderTest}, and
+ clear it.
+ """
+ self.lastTransaction.abort()
+ self.lastTransaction = None
+
+ def setUp(self):
+ self.notifierFactory = StubNotifierFactory()
+
+ def tearDown(self):
+ if self.lastTransaction is not None:
+ self.commit()
+
+
+
+ def homeUnderTest(self):
+ """
+ Get the addressbook home detailed by C{requirements['home1']}.
+ """
+ return self.transactionUnderTest().addressbookHomeWithUID("home1")
+
+
+ def addressbookUnderTest(self):
+ """
+ Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
+ """
+ return self.homeUnderTest().addressbookWithName("addressbook_1")
+
+
+ def addressbookObjectUnderTest(self):
+ """
+ Get the addressbook detailed by
+ C{requirements['home1']['addressbook_1']['1.vcf']}.
+ """
+ return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
+
+
+ assertProvides = assertProvides
+
+
+ def test_addressbookStoreProvides(self):
+ """
+ The addressbook store provides L{IAddressBookStore} and its required
+ attributes.
+ """
+ addressbookStore = self.storeUnderTest()
+ self.assertProvides(IDataStore, addressbookStore)
+
+
+ def test_transactionProvides(self):
+ """
+ The transactions generated by the addressbook store provide
+ L{IAddressBookStoreTransaction} and its required attributes.
+ """
+ txn = self.transactionUnderTest()
+ self.assertProvides(ICommonTransaction, txn)
+ self.assertProvides(IAddressBookTransaction, txn)
+
+
+ def test_homeProvides(self):
+ """
+ The addressbook homes generated by the addressbook store provide
+ L{IAddressBookHome} and its required attributes.
+ """
+ self.assertProvides(IAddressBookHome, self.homeUnderTest())
+
+
+ def test_addressbookProvides(self):
+ """
+ The addressbooks generated by the addressbook store provide L{IAddressBook} and
+ its required attributes.
+ """
+ self.assertProvides(IAddressBook, self.addressbookUnderTest())
+
+
+ def test_addressbookObjectProvides(self):
+ """
+ The addressbook objects generated by the addressbook store provide
+ L{IAddressBookObject} and its required attributes.
+ """
+ self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
+
+ def test_notifierID(self):
+ home = self.homeUnderTest()
+ self.assertEquals(home.notifierID(), "home1")
+ addressbook = home.addressbookWithName("addressbook_1")
+ self.assertEquals(addressbook.notifierID(), "home1")
+ self.assertEquals(addressbook.notifierID(label="collection"), "home1/addressbook_1")
+
+
+ def test_addressbookHomeWithUID_exists(self):
+ """
+ Finding an existing addressbook home by UID results in an object that
+ provides L{IAddressBookHome} and has a C{uid()} method that returns the
+ same value that was passed in.
+ """
+ addressbookHome = (self.transactionUnderTest()
+ .addressbookHomeWithUID("home1"))
+ self.assertEquals(addressbookHome.uid(), "home1")
+ self.assertProvides(IAddressBookHome, addressbookHome)
+
+
+ def test_addressbookHomeWithUID_absent(self):
+ """
+ L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
+ when asked for a non-existent addressbook home.
+ """
+ txn = self.transactionUnderTest()
+ self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
+
+
+ def test_addressbookWithName_exists(self):
+ """
+ L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
+ whose name matches the one passed in.
+ """
+ home = self.homeUnderTest()
+ for name in home1_addressbookNames:
+ addressbook = home.addressbookWithName(name)
+ if addressbook is None:
+ self.fail("addressbook %r didn't exist" % (name,))
+ self.assertProvides(IAddressBook, addressbook)
+ self.assertEquals(addressbook.name(), name)
+
+
+ def test_addressbookRename(self):
+ """
+ L{IAddressBook.rename} changes the name of the L{IAddressBook}.
+ """
+ home = self.homeUnderTest()
+ addressbook = home.addressbookWithName("addressbook_1")
+ addressbook.rename("some_other_name")
+ def positiveAssertions():
+ self.assertEquals(addressbook.name(), "some_other_name")
+ self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
+ self.assertEquals(None, home.addressbookWithName("addressbook_1"))
+ positiveAssertions()
+ self.commit()
+ home = self.homeUnderTest()
+ addressbook = home.addressbookWithName("some_other_name")
+ positiveAssertions()
+ # FIXME: revert
+ # FIXME: test for multiple renames
+ # FIXME: test for conflicting renames (a->b, c->a in the same txn)
+
+
+ def test_addressbookWithName_absent(self):
+ """
+ L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
+ do not exist.
+ """
+ self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
+ None)
+
+
+ def test_createAddressBookWithName_absent(self):
+ """
+ L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
+ can be retrieved with L{IAddressBookHome.addressbookWithName}.
+ """
+ home = self.homeUnderTest()
+ name = "new"
+ self.assertIdentical(home.addressbookWithName(name), None)
+ home.createAddressBookWithName(name)
+ self.assertNotIdentical(home.addressbookWithName(name), None)
+ def checkProperties():
+ addressbookProperties = home.addressbookWithName(name).properties()
+ self.assertEquals(
+ addressbookProperties[
+ PropertyName.fromString(davxml.ResourceType.sname())
+ ],
+ davxml.ResourceType.addressbook
+ ) #@UndefinedVariable
+ checkProperties()
+ self.commit()
+
+ # Make sure notification fired after commit
+ 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.addressbookWithName(name), None)
+
+ # FIXME: These two lines aren't in the calendar common tests:
+ # home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
+ # "home1")
+
+ # Sanity check: are the properties actually persisted?
+ # FIXME: no independent testing of this right now
+ checkProperties()
+
+
+ def test_createAddressBookWithName_exists(self):
+ """
+ L{IAddressBookHome.createAddressBookWithName} raises
+ L{AddressBookAlreadyExistsError} when the name conflicts with an already-
+ existing address book.
+ """
+ for name in home1_addressbookNames:
+ self.assertRaises(
+ HomeChildNameAlreadyExistsError,
+ self.homeUnderTest().createAddressBookWithName, name
+ )
+
+
+ def test_removeAddressBookWithName_exists(self):
+ """
+ L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
+ exists.
+ """
+ home = self.homeUnderTest()
+ # FIXME: test transactions
+ for name in home1_addressbookNames:
+ self.assertNotIdentical(home.addressbookWithName(name), None)
+ home.removeAddressBookWithName(name)
+ self.assertEquals(home.addressbookWithName(name), None)
+
+ self.commit()
+
+ # Make sure notification fired after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [("update", "home1"), ("update", "home1"), ("update", "home1")]
+ )
+
+
+ def test_removeAddressBookWithName_absent(self):
+ """
+ Attempt to remove an non-existing addressbook should raise.
+ """
+ home = self.homeUnderTest()
+ self.assertRaises(NoSuchHomeChildError,
+ home.removeAddressBookWithName, "xyzzy")
+
+
+ def test_addressbookObjects(self):
+ """
+ L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
+ in the filesystem, in name order, but skip those with hidden names.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ addressbookObjects = list(addressbook1.addressbookObjects())
+
+ for addressbookObject in addressbookObjects:
+ self.assertProvides(IAddressBookObject, addressbookObject)
+ self.assertEquals(
+ addressbook1.addressbookObjectWithName(addressbookObject.name()),
+ addressbookObject
+ )
+
+ self.assertEquals(
+ set(o.name() for o in addressbookObjects),
+ set(addressbook1_objectNames)
+ )
+
+
+ def test_addressbookObjectsWithRemovedObject(self):
+ """
+ L{IAddressBook.addressbookObjects} skips those objects which have been
+ removed by L{AddressBook.removeAddressBookObjectWithName} in the same
+ transaction, even if it has not yet been committed.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ addressbook1.removeAddressBookObjectWithName("2.vcf")
+ addressbookObjects = list(addressbook1.addressbookObjects())
+ self.assertEquals(set(o.name() for o in addressbookObjects),
+ set(addressbook1_objectNames) - set(["2.vcf"]))
+
+
+ def test_ownerAddressBookHome(self):
+ """
+ L{IAddressBook.ownerAddressBookHome} should match the home UID.
+ """
+ self.assertEquals(
+ self.addressbookUnderTest().ownerAddressBookHome().uid(),
+ self.homeUnderTest().uid()
+ )
+
+
+ def test_addressbookObjectWithName_exists(self):
+ """
+ L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
+ provider for addressbooks which already exist.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ for name in addressbook1_objectNames:
+ addressbookObject = addressbook1.addressbookObjectWithName(name)
+ self.assertProvides(IAddressBookObject, addressbookObject)
+ self.assertEquals(addressbookObject.name(), name)
+ # FIXME: add more tests based on CommonTests.requirements
+
+
+ def test_addressbookObjectWithName_absent(self):
+ """
+ L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
+ don't exist.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
+
+
+ def test_removeAddressBookObjectWithUID_exists(self):
+ """
+ Remove an existing addressbook object.
+ """
+ addressbook = self.addressbookUnderTest()
+ for name in addressbook1_objectNames:
+ uid = (u'uid' + name.rstrip(".vcf"))
+ self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
+ None)
+ addressbook.removeAddressBookObjectWithUID(uid)
+ self.assertEquals(
+ addressbook.addressbookObjectWithUID(uid),
+ None
+ )
+ self.assertEquals(
+ addressbook.addressbookObjectWithName(name),
+ None
+ )
+
+
+ def test_removeAddressBookObjectWithName_exists(self):
+ """
+ Remove an existing addressbook object.
+ """
+ addressbook = self.addressbookUnderTest()
+ for name in addressbook1_objectNames:
+ self.assertNotIdentical(
+ addressbook.addressbookObjectWithName(name), None
+ )
+ addressbook.removeAddressBookObjectWithName(name)
+ self.assertIdentical(
+ addressbook.addressbookObjectWithName(name), None
+ )
+
+ # Make sure notifications are fired after commit
+ self.commit()
+ self.assertEquals(
+ self.notifierFactory.history,
+ [
+ ("update", "home1"),
+ ("update", "home1/addressbook_1"),
+ ("update", "home1"),
+ ("update", "home1/addressbook_1"),
+ ("update", "home1"),
+ ("update", "home1/addressbook_1"),
+ ]
+ )
+
+
+ def test_removeAddressBookObjectWithName_absent(self):
+ """
+ Attempt to remove an non-existing addressbook object should raise.
+ """
+ addressbook = self.addressbookUnderTest()
+ self.assertRaises(
+ NoSuchObjectResourceError,
+ addressbook.removeAddressBookObjectWithName, "xyzzy"
+ )
+
+
+ def test_addressbookName(self):
+ """
+ L{AddressBook.name} reflects the name of the addressbook.
+ """
+ self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
+
+
+ def test_addressbookObjectName(self):
+ """
+ L{IAddressBookObject.name} reflects the name of the addressbook object.
+ """
+ self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
+
+
+ def test_component(self):
+ """
+ L{IAddressBookObject.component} returns a L{VComponent} describing the
+ addressbook data underlying that addressbook object.
+ """
+ component = self.addressbookObjectUnderTest().component()
+
+ self.failUnless(
+ isinstance(component, VComponent),
+ component
+ )
+
+ self.assertEquals(component.name(), "VCARD")
+ self.assertEquals(component.resourceUID(), "uid1")
+
+
+ def test_iAddressBookText(self):
+ """
+ L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
+ data provided by L{IAddressBookObject.component}.
+ """
+ text = self.addressbookObjectUnderTest().vCardText()
+ self.assertIsInstance(text, str)
+ self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
+ self.assertIn("\r\nUID:uid1\r\n", text)
+ self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
+
+
+ def test_addressbookObjectUID(self):
+ """
+ L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
+ of the addressbook object's component.
+ """
+ self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
+
+
+ def test_addressbookObjectWithUID_absent(self):
+ """
+ L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
+ don't exist.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
+
+
+ def test_addressbooks(self):
+ """
+ L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
+ providers, which are consistent with the results from
+ L{IAddressBook.addressbookWithName}.
+ """
+ # Add a dot directory to make sure we don't find it
+ # self.home1._path.child(".foo").createDirectory()
+ home = self.homeUnderTest()
+ addressbooks = list(home.addressbooks())
+
+ for addressbook in addressbooks:
+ self.assertProvides(IAddressBook, addressbook)
+ self.assertEquals(addressbook,
+ home.addressbookWithName(addressbook.name()))
+
+ self.assertEquals(
+ set(c.name() for c in addressbooks),
+ set(home1_addressbookNames)
+ )
+
+
+ def test_addressbooksAfterAddAddressBook(self):
+ """
+ L{IAddressBookHome.addressbooks} includes addressbooks recently added with
+ L{IAddressBookHome.createAddressBookWithName}.
+ """
+ home = self.homeUnderTest()
+ before = set(x.name() for x in home.addressbooks())
+ home.createAddressBookWithName("new-name")
+ after = set(x.name() for x in home.addressbooks())
+ self.assertEquals(before | set(['new-name']), after)
+
+
+ def test_createAddressBookObjectWithName_absent(self):
+ """
+ L{IAddressBook.createAddressBookObjectWithName} creates a new
+ L{IAddressBookObject}.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ name = "4.vcf"
+ self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
+ component = VComponent.fromString(vcard4_text)
+ addressbook1.createAddressBookObjectWithName(name, component)
+
+ addressbookObject = addressbook1.addressbookObjectWithName(name)
+ self.assertEquals(addressbookObject.component(), component)
+
+ self.commit()
+
+ # Make sure notifications fire after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [
+ ("update", "home1"),
+ ("update", "home1/addressbook_1"),
+ ]
+ )
+
+
+ def test_createAddressBookObjectWithName_exists(self):
+ """
+ L{IAddressBook.createAddressBookObjectWithName} raises
+ L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
+ given name already exists in that addressbook.
+ """
+ self.assertRaises(
+ ObjectResourceNameAlreadyExistsError,
+ self.addressbookUnderTest().createAddressBookObjectWithName,
+ "1.vcf", VComponent.fromString(vcard4_text)
+ )
+
+
+ def test_createAddressBookObjectWithName_invalid(self):
+ """
+ L{IAddressBook.createAddressBookObjectWithName} raises
+ L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
+ text.
+ """
+ self.assertRaises(
+ InvalidObjectResourceError,
+ self.addressbookUnderTest().createAddressBookObjectWithName,
+ "new", VComponent.fromString(vcard4notCardDAV_text)
+ )
+
+
+ def test_setComponent_invalid(self):
+ """
+ L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
+ presented with invalid iAddressBook text.
+ """
+ addressbookObject = self.addressbookObjectUnderTest()
+ self.assertRaises(
+ InvalidObjectResourceError,
+ addressbookObject.setComponent,
+ VComponent.fromString(vcard4notCardDAV_text)
+ )
+
+
+ def test_setComponent_uidchanged(self):
+ """
+ L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
+ when given a L{VComponent} whose UID does not match its existing UID.
+ """
+ addressbook1 = self.addressbookUnderTest()
+ component = VComponent.fromString(vcard4_text)
+ addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+ self.assertRaises(
+ InvalidObjectResourceError,
+ addressbookObject.setComponent, component
+ )
+
+
+ def test_addressbookHomeWithUID_create(self):
+ """
+ L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
+ will create a addressbook home that doesn't exist yet.
+ """
+ txn = self.transactionUnderTest()
+ noHomeUID = "xyzzy"
+ addressbookHome = txn.addressbookHomeWithUID(
+ noHomeUID,
+ create=True
+ )
+ def readOtherTxn():
+ otherTxn = self.savedStore.newTransaction()
+ self.addCleanup(otherTxn.commit)
+ return otherTxn.addressbookHomeWithUID(noHomeUID)
+ self.assertProvides(IAddressBookHome, addressbookHome)
+ # A concurrent transaction shouldn't be able to read it yet:
+ self.assertIdentical(readOtherTxn(), None)
+ self.commit()
+ # But once it's committed, other transactions should see it.
+ self.assertProvides(IAddressBookHome, readOtherTxn())
+
+
+ def test_setComponent(self):
+ """
+ L{AddressBookObject.setComponent} changes the result of
+ L{AddressBookObject.component} within the same transaction.
+ """
+ component = VComponent.fromString(vcard1modified_text)
+
+ addressbook1 = self.addressbookUnderTest()
+ addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+ oldComponent = addressbookObject.component()
+ self.assertNotEqual(component, oldComponent)
+ addressbookObject.setComponent(component)
+ self.assertEquals(addressbookObject.component(), component)
+
+ # Also check a new instance
+ addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
+ self.assertEquals(addressbookObject.component(), component)
+
+ self.commit()
+
+ # Make sure notification fired after commit
+ self.assertEquals(
+ self.notifierFactory.history,
+ [
+ ("update", "home1"),
+ ("update", "home1/addressbook_1"),
+ ]
+ )
+
+ def checkPropertiesMethod(self, thunk):
+ """
+ Verify that the given object has a properties method that returns an
+ L{IPropertyStore}.
+ """
+ properties = thunk.properties()
+ self.assertProvides(IPropertyStore, properties)
+
+
+ def test_homeProperties(self):
+ """
+ L{IAddressBookHome.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.homeUnderTest())
+
+
+ def test_addressbookProperties(self):
+ """
+ L{IAddressBook.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.addressbookUnderTest())
+
+
+ def test_addressbookObjectProperties(self):
+ """
+ L{IAddressBookObject.properties} returns a property store.
+ """
+ self.checkPropertiesMethod(self.addressbookObjectUnderTest())
+
+
+ def test_newAddressBookObjectProperties(self):
+ """
+ L{IAddressBookObject.properties} returns an empty property store for a
+ addressbook object which has been created but not committed.
+ """
+ addressbook = self.addressbookUnderTest()
+ addressbook.createAddressBookObjectWithName(
+ "4.vcf", VComponent.fromString(vcard4_text)
+ )
+ newEvent = addressbook.addressbookObjectWithName("4.vcf")
+ self.assertEquals(newEvent.properties().items(), [])
+
+
+ def test_setComponentPreservesProperties(self):
+ """
+ L{IAddressBookObject.setComponent} preserves properties.
+
+ (Some implementations must go to extra trouble to provide this
+ behavior; for example, file storage must copy extended attributes from
+ the existing file to the temporary file replacing it.)
+ """
+ propertyName = PropertyName("http://example.com/ns", "example")
+ propertyContent = WebDAVUnknownElement("sample content")
+ propertyContent.name = propertyName.name
+ propertyContent.namespace = propertyName.namespace
+
+ self.addressbookObjectUnderTest().properties()[
+ propertyName] = propertyContent
+ self.commit()
+ # Sanity check; are properties even readable in a separate transaction?
+ # Should probably be a separate test.
+ self.assertEquals(
+ self.addressbookObjectUnderTest().properties()[propertyName],
+ propertyContent)
+ obj = self.addressbookObjectUnderTest()
+ vcard1_text = obj.vCardText()
+ vcard1_text_withDifferentNote = vcard1_text.replace(
+ "NOTE:CardDAV protocol updates",
+ "NOTE:Changed"
+ )
+ # Sanity check; make sure the test has the right idea of the subject.
+ self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
+ newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
+ obj.setComponent(newComponent)
+
+ # Putting everything into a separate transaction to account for any
+ # caching that may take place.
+ self.commit()
+ self.assertEquals(
+ self.addressbookObjectUnderTest().properties()[propertyName],
+ propertyContent
+ )
+
+
+ def test_dontLeakAddressbooks(self):
+ """
+ Addressbooks in one user's addressbook home should not show up in another
+ user's addressbook home.
+ """
+ home2 = self.transactionUnderTest().addressbookHomeWithUID(
+ "home2", create=True)
+ self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
+
+
+ def test_dontLeakObjects(self):
+ """
+ Addressbook objects in one user's addressbook should not show up in another
+ user's via uid or name queries.
+ """
+ home1 = self.homeUnderTest()
+ home2 = self.transactionUnderTest().addressbookHomeWithUID(
+ "home2", create=True)
+ addressbook1 = home1.addressbookWithName("addressbook_1")
+ addressbook2 = home2.addressbookWithName("addressbook")
+ objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
+ self.assertEquals(objects, [])
+ for resourceName in self.requirements['home1']['addressbook_1'].keys():
+ obj = addressbook1.addressbookObjectWithName(resourceName)
+ self.assertIdentical(
+ addressbook2.addressbookObjectWithName(resourceName), None)
+ self.assertIdentical(
+ addressbook2.addressbookObjectWithUID(obj.uid()), None)
+
+
+
+class StubNotifierFactory(object):
+
+ """ For testing push notifications without an XMPP server """
+
+ def __init__(self):
+ self.reset()
+
+ def newNotifier(self, label="default", id=None):
+ return Notifier(self, label=label, id=id)
+
+ def send(self, op, id):
+ self.history.append((op, id))
+
+ def reset(self):
+ self.history = []
Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/test_file.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -29,10 +29,10 @@
from txdav.common.icommondatastore import NoSuchHomeChildError
from txdav.common.icommondatastore import NoSuchObjectResourceError
-from txcarddav.addressbookstore.file import AddressBookStore, AddressBookHome
-from txcarddav.addressbookstore.file import AddressBook, AddressBookObject
+from txdav.carddav.datastore.file import AddressBookStore, AddressBookHome
+from txdav.carddav.datastore.file import AddressBook, AddressBookObject
-from txcarddav.addressbookstore.test.common import (
+from txdav.carddav.datastore.test.common import (
CommonTests, vcard4_text, vcard1modified_text, StubNotifierFactory)
storePath = FilePath(__file__).parent().child("addressbook_store")
Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/test/test_sql.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,75 @@
+##
+# 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 txdav.caldav.datastore.postgres, mostly based on
+L{txdav.caldav.datastore.test.common}.
+"""
+
+from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
+
+from txdav.common.datastore.test.util import SQLStoreBuilder
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.vcard import Component as VCard
+
+
+theStoreBuilder = SQLStoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+class AddressBookSQLStorageTests(AddressBookCommonTests, unittest.TestCase):
+ """
+ AddressBook SQL storage tests.
+ """
+
+ @inlineCallbacks
+ def setUp(self):
+ super(AddressBookSQLStorageTests, self).setUp()
+ self.addressbookStore = yield buildStore(self, self.notifierFactory)
+ self.populate()
+
+ def populate(self):
+ populateTxn = self.addressbookStore.newTransaction()
+ for homeUID in self.requirements:
+ addressbooks = self.requirements[homeUID]
+ if addressbooks is not None:
+ home = populateTxn.addressbookHomeWithUID(homeUID, True)
+ # We don't want the default addressbook to appear unless it's
+ # explicitly listed.
+ home.removeAddressBookWithName("addressbook")
+ for addressbookName in addressbooks:
+ addressbookObjNames = addressbooks[addressbookName]
+ if addressbookObjNames is not None:
+ home.createAddressBookWithName(addressbookName)
+ addressbook = home.addressbookWithName(addressbookName)
+ for objectName in addressbookObjNames:
+ objData = addressbookObjNames[objectName]
+ addressbook.createAddressBookObjectWithName(
+ objectName, VCard.fromString(objData)
+ )
+
+ populateTxn.commit()
+ self.notifierFactory.reset()
+
+
+
+ def storeUnderTest(self):
+ """
+ Create and return a L{AddressBookStore} for testing.
+ """
+ return self.addressbookStore
+
Copied: CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py (from rev 6184, CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/util.py)
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/util.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -0,0 +1,58 @@
+##
+# 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.
+##
+
+"""
+Utility logic common to multiple backend implementations.
+"""
+
+from twistedcaldav.vcard import Component as VCard
+from twistedcaldav.vcard import InvalidVCardDataError
+
+from txdav.common.icommondatastore import InvalidObjectResourceError,\
+ NoSuchObjectResourceError
+
+def validateAddressBookComponent(addressbookObject, vcard, component, inserting):
+ """
+ Validate an addressbook component for a particular addressbook.
+
+ @param addressbookObject: The addressbook object whose component will be replaced.
+ @type addressbookObject: L{IAddressBookObject}
+
+ @param addressbook: The addressbook which the L{IAddressBookObject} is present in.
+ @type addressbook: L{IAddressBook}
+
+ @param component: The VComponent to be validated.
+ @type component: L{VComponent}
+ """
+
+ if not isinstance(component, VCard):
+ raise TypeError(type(component))
+
+ try:
+ if not inserting and component.resourceUID() != addressbookObject.uid():
+ raise InvalidObjectResourceError(
+ "UID may not change (%s != %s)" % (
+ component.resourceUID(), addressbookObject.uid()
+ )
+ )
+ except NoSuchObjectResourceError:
+ pass
+
+ try:
+ component.validForCardDAV()
+ except InvalidVCardDataError, e:
+ raise InvalidObjectResourceError(e)
+
\ No newline at end of file
Modified: CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/iaddressbookstore.py 2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/carddav/iaddressbookstore.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore,txcaldav.calendarstore.test.test_postgres.AddressBookSQLStorageTests -*-
+# -*- test-case-name: txdav.carddav.datastore,txdav.caldav.datastore.test.test_postgres.AddressBookSQLStorageTests -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_file -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
@@ -110,10 +110,10 @@
@type dataStore: L{CommonDataStore}
"""
- from txcaldav.icalendarstore import ICalendarTransaction
- from txcarddav.iaddressbookstore import IAddressBookTransaction
- from txcaldav.calendarstore.file import CalendarHome
- from txcarddav.addressbookstore.file import AddressBookHome
+ from txdav.caldav.icalendarstore import ICalendarTransaction
+ from txdav.carddav.iaddressbookstore import IAddressBookTransaction
+ from txdav.caldav.datastore.file import CalendarHome
+ from txdav.carddav.datastore.file import AddressBookHome
super(CommonStoreTransaction, self).__init__(dataStore, name)
self._homes = {}
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -36,9 +36,9 @@
from twistedcaldav.customxml import NotificationType
from txdav.common.datastore.sql_legacy import PostgresLegacyNotificationsEmulator
-from txcaldav.icalendarstore import ICalendarTransaction
+from txdav.caldav.icalendarstore import ICalendarTransaction
-from txcarddav.iaddressbookstore import IAddressBookTransaction
+from txdav.carddav.iaddressbookstore import IAddressBookTransaction
from txdav.common.datastore.sql_tables import CALENDAR_HOME_TABLE,\
ADDRESSBOOK_HOME_TABLE, NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN,\
@@ -117,8 +117,8 @@
extraInterfaces.append(IAddressBookTransaction)
directlyProvides(self, *extraInterfaces)
- from txcaldav.calendarstore.sql import CalendarHome
- from txcarddav.addressbookstore.sql import AddressBookHome
+ from txdav.caldav.datastore.sql import CalendarHome
+ from txdav.carddav.datastore.sql import AddressBookHome
CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_legacy.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql_tables.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+# -*- test-case-name: txdav.caldav.datastore.test.test_postgres -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/__init__.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py 2010-08-26 01:11:33 UTC (rev 6185)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/test/util.py 2010-08-26 01:34:38 UTC (rev 6186)
@@ -1,4 +1,4 @@
-# -*- test-case-name: txcarddav.addressbookstore.test -*-
+# -*- test-case-name: txdav.carddav.datastore.test -*-
##
# Copyright (c) 2010 Apple Inc. All rights reserved.
#
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100825/8115aa33/attachment-0001.html>
More information about the calendarserver-changes
mailing list