[CalendarServer-changes] [9232] CalendarServer/trunk/txdav
source_changes at macosforge.org
source_changes at macosforge.org
Tue May 8 14:26:12 PDT 2012
Revision: 9232
http://trac.macosforge.org/projects/calendarserver/changeset/9232
Author: glyph at apple.com
Date: 2012-05-08 14:26:12 -0700 (Tue, 08 May 2012)
Log Message:
-----------
Add Calendar.asShared(), a nice data-store API for inspecting the sharee's view of a given calendar.
Modified Paths:
--------------
CalendarServer/trunk/txdav/caldav/datastore/file.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
CalendarServer/trunk/txdav/caldav/icalendarstore.py
CalendarServer/trunk/txdav/carddav/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/icommondatastore.py
Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -265,7 +265,16 @@
return ResourceType.calendar #@UndefinedVariable
+ def asShared(self):
+ """
+ Stub for interface-compliance tests.
+ """
+ # TODO: implement me.
+ raise NotImplementedError()
+
+
ownerCalendarHome = CommonHomeChild.ownerHome
+ viewerCalendarHome = CommonHomeChild.viewerHome
calendarObjects = CommonHomeChild.objectResources
listCalendarObjects = CommonHomeChild.listObjectResources
calendarObjectWithName = CommonHomeChild.objectResourceWithName
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -51,7 +51,7 @@
from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
IAttachment
from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
- CommonObjectResource
+ CommonObjectResource, CommonStoreTransaction, ECALENDARTYPE
from txdav.common.datastore.sql_legacy import \
PostgresLegacyIndexEmulator, SQLLegacyCalendarInvites,\
SQLLegacyCalendarShares, PostgresLegacyInboxIndexEmulator
@@ -314,13 +314,19 @@
newname = str(uuid.uuid4())
newcal = yield self.createCalendarWithName(newname)
yield newcal.setSupportedComponents(support_component)
-
+
yield _requireCalendarWithType("VEVENT", "calendar")
yield _requireCalendarWithType("VTODO", "tasks")
-
+
+
+
+CalendarHome._register(ECALENDARTYPE)
+
+
+
class Calendar(CommonHomeChild):
"""
- File-based implementation of L{ICalendar}.
+ SQL-based implementation of L{ICalendar}.
"""
implements(ICalendar)
@@ -340,26 +346,18 @@
_revisionsBindTable = CALENDAR_OBJECT_REVISIONS_AND_BIND_TABLE
_objectTable = CALENDAR_OBJECT_TABLE
- def __init__(self, home, name, resourceID, owned):
+ _supportedComponents = None
+
+ def __init__(self, *args, **kw):
"""
Initialize a calendar pointing at a record in a database.
-
- @param name: the name of the calendar resource.
- @type name: C{str}
-
- @param home: the home containing this calendar.
- @type home: L{CalendarHome}
"""
- super(Calendar, self).__init__(home, name, resourceID, owned)
-
- if name == 'inbox':
+ super(Calendar, self).__init__(*args, **kw)
+ if self.name() == 'inbox':
self._index = PostgresLegacyInboxIndexEmulator(self)
else:
self._index = PostgresLegacyIndexEmulator(self)
self._invites = SQLLegacyCalendarInvites(self)
- self._objectResourceClass = CalendarObject
-
- self._supportedComponents = None
@classmethod
@@ -405,6 +403,7 @@
ownerCalendarHome = CommonHomeChild.ownerHome
+ viewerCalendarHome = CommonHomeChild.viewerHome
calendarObjects = CommonHomeChild.objectResources
listCalendarObjects = CommonHomeChild.listObjectResources
calendarObjectWithName = CommonHomeChild.objectResourceWithName
@@ -1428,3 +1427,4 @@
return self._modified
+Calendar._objectResourceClass = CalendarObject
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -1070,6 +1070,27 @@
@inlineCallbacks
+ def test_asShared(self):
+ """
+ L{ICalendar.asShared} returns an iterable of all versions of a shared
+ calendar.
+ """
+ cal = yield self.calendarUnderTest()
+ sharedBefore = yield cal.asShared()
+ # It's not shared yet; make sure asShared doesn't include owner version.
+ self.assertEqual(len(sharedBefore), 0)
+ yield self.test_shareWith()
+ # FIXME: don't know why this separate transaction is needed; remove it.
+ yield self.commit()
+ cal = yield self.calendarUnderTest()
+ sharedAfter = yield cal.asShared()
+ self.assertEqual(len(sharedAfter), 1)
+ self.assertEqual(sharedAfter[0].shareMode(), _BIND_MODE_WRITE)
+ self.assertEqual(sharedAfter[0].viewerCalendarHome().uid(),
+ OTHER_HOME_UID)
+
+
+ @inlineCallbacks
def test_hasCalendarResourceUIDSomewhereElse(self):
"""
L{ICalendarHome.hasCalendarResourceUIDSomewhereElse} will determine if
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -470,10 +470,13 @@
Overridden to be skipped.
"""
+
+ # TODO: ideally the file store would support all of this sharing stuff.
test_shareWith.skip = "Not implemented for file store yet."
test_shareAgainChangesMode = test_shareWith
test_unshareWith = test_shareWith
test_unshareWithInDifferentTransaction = test_shareWith
+ test_asShared = test_shareWith
def test_init(self):
Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -26,7 +26,15 @@
from twisted.internet.interfaces import ITransport
from txdav.idav import INotifier
+# This is pulling in a bit much for an interfaces module, but currently the bind
+# modes are defined in the schema.
+from txdav.common.datastore.sql_tables import _BIND_MODE_OWN as BIND_OWN
+from txdav.common.datastore.sql_tables import _BIND_MODE_READ as BIND_READ
+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE as BIND_WRITE
+from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT as BIND_DIRECT
+
+
__all__ = [
# Interfaces
"ICalendarTransaction",
@@ -36,6 +44,12 @@
# Exceptions
"QuotaExceeded",
+
+ # Enumerations
+ "BIND_OWN",
+ "BIND_READ",
+ "BIND_WRITE",
+ "BIND_DIRECT",
]
@@ -226,12 +240,18 @@
Change the name of this calendar.
"""
+
def ownerCalendarHome():
"""
- Retrieve the calendar home for the owner of this calendar.
- Calendars may be shared from one (the owner's) calendar home
- to other (the sharee's) calendar homes.
+ Retrieve the calendar home for the owner of this calendar. Calendars
+ may be shared from one (the owner's) calendar home to other (the
+ sharee's) calendar homes.
+ FIXME: implementations of this method currently do not behave as
+ documented; a sharee's home, rather than the owner's home, may be
+ returned in some cases. Current usages should likely be changed to use
+ viewerCalendarHome() instead.
+
@return: an L{ICalendarHome}.
"""
@@ -337,7 +357,52 @@
"""
+ def asShared():
+ """
+ Get a view of this L{ICalendar} as present in everyone's calendar home
+ except for its owner's.
+ @return: a L{Deferred} which fires with a list of L{ICalendar}s, each
+ L{ICalendar} as seen by its respective sharee. This means that its
+ C{shareMode} will be something other than L{BIND_OWN}, and its
+ L{ICalendar.viewerCalendarHome} will return the home of the sharee.
+ """
+
+
+ def shareMode():
+ """
+ The sharing mode of this calendar; one of the C{BIND_*} constants in
+ this module.
+
+ @see: L{ICalendar.viewerCalendarHome}
+ """
+ # TODO: implement this for the file store.
+
+
+ def viewerCalendarHome():
+ """
+ Retrieve the calendar home for the viewer of this calendar. In other
+ words, the calendar home that this L{ICalendar} was retrieved through.
+
+ For example: if Alice shares her calendar with Bob,
+ C{txn.calendarHomeWithUID("alice") ...
+ .calendarWithName("calendar").viewerCalendarHome()} will return Alice's
+ home, whereas C{txn.calendarHomeWithUID("bob") ...
+ .sharedChildWithName("alice's calendar").viewerCalendarHome()} will
+ return Bob's calendar home.
+
+ @return: (synchronously) the calendar home of the user into which this
+ L{ICalendar} is bound.
+ @rtype: L{ICalendarHome}
+ """
+ # TODO: implement this for the file store.
+
+ # TODO: implement home-child- retrieval APIs to retrieve shared items
+ # from the store; the example in the docstring ought to be
+ # calendarWithName not sharedChildWithName.
+
+
+
class ICalendarObject(IDataStoreObject):
"""
Calendar object
Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -47,9 +47,10 @@
IAddressBookObject
from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
- CommonObjectResource
+ CommonObjectResource, EADDRESSBOOKTYPE
from twext.enterprise.dal.syntax import Delete
from twext.enterprise.dal.syntax import Insert
+
from twext.enterprise.dal.syntax import Update
from twext.enterprise.dal.syntax import utcNowSQL
from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE,\
@@ -136,9 +137,13 @@
+AddressBookHome._register(EADDRESSBOOKTYPE)
+
+
+
class AddressBook(CommonHomeChild):
"""
- File-based implementation of L{IAddressBook}.
+ SQL-based implementation of L{IAddressBook}.
"""
implements(IAddressBook)
@@ -157,27 +162,10 @@
_revisionsBindTable = ADDRESSBOOK_OBJECT_REVISIONS_AND_BIND_TABLE
_objectTable = ADDRESSBOOK_OBJECT_TABLE
- def __init__(self, home, name, resourceID, owned):
- """
- 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, owned)
-
+ def __init__(self, *args, **kw):
+ super(AddressBook, self).__init__(*args, **kw)
self._index = PostgresLegacyABIndexEmulator(self)
self._invites = SQLLegacyAddressBookInvites(self)
- self._objectResourceClass = AddressBookObject
@property
@@ -340,3 +328,4 @@
+AddressBook._objectResourceClass = AddressBookObject
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -36,7 +36,7 @@
from twistedcaldav.notifications import NotificationRecord
from twistedcaldav.notifications import NotificationsDatabase as OldNotificationIndex
from twistedcaldav.sharing import SharedCollectionsDatabase
-from txdav.caldav.icalendarstore import ICalendarStore
+from txdav.caldav.icalendarstore import ICalendarStore, BIND_OWN
from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
@@ -119,7 +119,7 @@
"""
Create a new transaction.
- @see Transaction
+ @see: L{Transaction}
"""
return self._transactionClass(
self,
@@ -693,6 +693,14 @@
return self._path.basename()
+ def shareMode(self):
+ """
+ Stub implementation of L{ICalendar.shareMode}; always returns
+ L{BIND_OWN}.
+ """
+ return BIND_OWN
+
+
_renamedName = None
@writeOperation
@@ -750,10 +758,15 @@
self.notifyChanged()
+
def ownerHome(self):
return self._home
+ def viewerHome(self):
+ return self._home
+
+
def setSharingUID(self, uid):
self.properties()._setPerUserUID(uid)
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -332,20 +332,31 @@
self._primaryHomeType = EADDRESSBOOKTYPE
directlyProvides(self, *extraInterfaces)
- from txdav.caldav.datastore.sql import CalendarHome
- from txdav.carddav.datastore.sql import AddressBookHome
- CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
- CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
+ self._circularImportHack()
self._sqlTxn = sqlTxn
self.paramstyle = sqlTxn.paramstyle
self.dialect = sqlTxn.dialect
-
+
self._stats = TransactionStatsCollector() if self._store.logStats else None
self.statementCount = 0
self.iudCount = 0
self.currentStatement = None
+ @classmethod
+ def _circularImportHack(cls):
+ """
+ This method is run when the first L{CommonStoreTransaction} is
+ instantiated, to populate class-scope (in other words, global) state
+ that requires importing modules which depend on this one.
+
+ @see: L{CommonHome._register}
+ """
+ if not cls._homeClass:
+ __import__("txdav.caldav.datastore.sql")
+ __import__("txdav.carddav.datastore.sql")
+
+
def store(self):
return self._store
@@ -387,24 +398,29 @@
return self._homeClass[storeType].homeWithUID(self, uid, create)
+
@inlineCallbacks
- def calendarHomeWithResourceID(self, rid):
- uid = (yield self._homeClass[ECALENDARTYPE].homeUIDWithResourceID(self, rid))
+ def homeWithResourceID(self, storeType, rid, create=False):
+ """
+ Load a calendar or addressbook home by its integer resource ID.
+ """
+ uid = (yield self._homeClass[storeType]
+ .homeUIDWithResourceID(self, rid))
if uid:
- result = (yield self.calendarHomeWithUID(uid))
+ result = (yield self.homeWithUID(storeType, uid, create))
else:
result = None
returnValue(result)
- @inlineCallbacks
+
+ def calendarHomeWithResourceID(self, rid):
+ return self.homeWithResourceID(ECALENDARTYPE, rid)
+
+
def addressbookHomeWithResourceID(self, rid):
- uid = (yield self._homeClass[EADDRESSBOOKTYPE].homeUIDWithResourceID(self, rid))
- if uid:
- result = (yield self.addressbookHomeWithUID(uid))
- else:
- result = None
- returnValue(result)
+ return self.homeWithResourceID(EADDRESSBOOKTYPE, rid)
+
@memoizedKey("uid", "_notificationHomes")
def notificationsWithUID(self, uid):
"""
@@ -855,6 +871,16 @@
self._revisionBindJoinTable["BIND:%s" % (key,)] = value
+ @classmethod
+ def _register(cls, homeType):
+ """
+ Register a L{CommonHome} subclass as its respective home type constant
+ with L{CommonStoreTransaction}.
+ """
+ cls._homeType = homeType
+ CommonStoreTransaction._homeClass[cls._homeType] = cls
+
+
def quotaAllowedBytes(self):
return self._txn.store().quota
@@ -1827,7 +1853,7 @@
_objectTable = None
- def __init__(self, home, name, resourceID, owned):
+ def __init__(self, home, name, resourceID, owned, mode):
if home._notifiers:
childID = "%s/%s" % (home.uid(), name)
@@ -1840,6 +1866,7 @@
self._name = name
self._resourceID = resourceID
self._owned = owned
+ self._bindMode = mode
self._created = None
self._modified = None
self._objects = {}
@@ -1927,8 +1954,8 @@
else:
ownedPiece = (bind.BIND_MODE != _BIND_MODE_OWN).And(
bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
-
- columns = [child.RESOURCE_ID, bind.RESOURCE_NAME,]
+
+ columns = [child.RESOURCE_ID, bind.RESOURCE_NAME, bind.BIND_MODE]
columns.extend(cls.metadataColumns())
return Select(columns,
From=child.join(
@@ -2053,14 +2080,62 @@
returnValue(resourceName)
+ def shareMode(self):
+ """
+ @see: L{ICalendar.shareMode}
+ """
+ return self._bindMode
+
+
+ @classproperty
+ def _bindEntriesFor(cls):
+ bind = cls._bindSchema
+ return Select([bind.BIND_MODE, bind.HOME_RESOURCE_ID,
+ bind.RESOURCE_NAME],
+ From=bind,
+ Where=(bind.RESOURCE_ID == Parameter("resourceID")).And
+ (bind.BIND_STATUS == _BIND_STATUS_ACCEPTED).And
+ (bind.BIND_MODE != _BIND_MODE_OWN))
+
+
+ @inlineCallbacks
+ def asShared(self):
+ """
+ Retrieve all the versions of this L{CommonHomeChild} as it is shared to
+ everyone.
+
+ @see: L{ICalendarHome.asShared}
+
+ @return: L{CommonHomeChild} objects that represent this
+ L{CommonHomeChild} as a child of different L{CommonHome}s
+ @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
+ """
+ rows = yield self._bindEntriesFor.on(self._txn,
+ resourceID=self._resourceID)
+ cls = self.__class__ # for ease of grepping...
+ result = []
+ for mode, homeResourceID, sharedResourceName in rows:
+ # TODO: this could all be issued in parallel; no need to serialize
+ # the loop.
+ new = cls(
+ (yield self._txn.homeWithResourceID(self._home._homeType,
+ homeResourceID)),
+ sharedResourceName, self._resourceID, False, mode
+ )
+ yield new.initFromStore()
+ result.append(new)
+ returnValue(result)
+
+
@classmethod
@inlineCallbacks
def loadAllObjects(cls, home, owned):
"""
- Load all child objects and return a list of them. This must create the
- child classes and initialize them using "batched" SQL operations to keep
- this constant wrt the number of children. This is an optimization for
- Depth:1 operations on the home.
+ Load all L{CommonHomeChild} instances which are children of a given
+ L{CommonHome} and return a L{Deferred} firing a list of them. This must
+ create the child classes and initialize them using "batched" SQL
+ operations to keep this constant wrt the number of children. This is an
+ optimization for Depth:1 operations on the home.
"""
results = []
@@ -2097,9 +2172,9 @@
# Create the actual objects merging in properties
for items in dataRows:
- resourceID, resource_name = items[:2]
- metadata = items[2:]
- child = cls(home, resource_name, resourceID, owned)
+ resourceID, resourceName, bindMode = items[:3]
+ metadata = items[3:]
+ child = cls(home, resourceName, resourceID, owned, bindMode)
for attr, value in zip(cls.metadataAttributes(), metadata):
setattr(child, attr, value)
child._syncTokenRevision = revisions[resourceID]
@@ -2118,7 +2193,7 @@
"""
bind = cls._bindSchema
return Select(
- [bind.RESOURCE_ID],
+ [bind.RESOURCE_ID, bind.BIND_MODE],
From=bind,
Where=(bind.RESOURCE_NAME == Parameter('objectName')).And(
bind.HOME_RESOURCE_ID == Parameter('homeID')).And(
@@ -2187,8 +2262,8 @@
if not data:
returnValue(None)
- resourceID = data[0][0]
- child = cls(home, name, resourceID, owned)
+ resourceID, mode = data[0]
+ child = cls(home, name, resourceID, owned, mode)
yield child.initFromStore()
returnValue(child)
@@ -2223,7 +2298,7 @@
if not data:
returnValue(None)
name, mode = data[0]
- child = cls(home, name, resourceID, mode == _BIND_MODE_OWN)
+ child = cls(home, name, resourceID, mode == _BIND_MODE_OWN, mode)
yield child.initFromStore()
returnValue(child)
@@ -2293,7 +2368,7 @@
)
# Initialize other state
- child = cls(home, name, resourceID, True)
+ child = cls(home, name, resourceID, True, _BIND_MODE_OWN)
child._created = _created
child._modified = _modified
yield child._loadPropertyStore()
@@ -2457,9 +2532,20 @@
def ownerHome(self):
+ """
+ (Don't use this method. See interface documentation as to why.)
+ """
return self._home
+ def viewerHome(self):
+ """
+ @see: L{ICalendar.viewerCalendarHome}
+ @see: L{IAddressbook.viewerAddressbookHome}
+ """
+ return self._home
+
+
@classproperty
def _ownerHomeFromResourceQuery(cls): #@NoSelf
"""
@@ -2476,6 +2562,12 @@
@inlineCallbacks
def sharerHomeID(self):
+ """
+ Retrieve the resource ID of the owner of this home.
+
+ @return: a L{Deferred} that fires with the resource ID.
+ @rtype: L{Deferred} firing L{int}
+ """
if self._owned:
# If this was loaded by its owner then we can skip the query, since
# we already know who the owner is.
Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py 2012-05-07 18:12:03 UTC (rev 9231)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py 2012-05-08 21:26:12 UTC (rev 9232)
@@ -236,7 +236,7 @@
This is a temporary shim method due to the way L{twistedcaldav.sharing}
works, which is that it expects to look in the 'sharesDB' object to
find what calendars are shared by whom, separately looks up the owner's
- calendar home based on that information, then sets the sharee's UID on
+ calendar home based on that information, then sets the sharee's UID on
that calendar, the main effect of which is to change the per-user uid
of the properties for that calendar object.
@@ -246,5 +246,5 @@
end can tell it's shared.
@param shareeUID: the UID of the sharee.
- @type shareeUID: C{str]
+ @type shareeUID: C{str}
"""
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120508/a226fad6/attachment-0001.html>
More information about the calendarserver-changes
mailing list