[CalendarServer-changes] [9944] CalendarServer/trunk/txdav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Oct 16 09:12:09 PDT 2012
Revision: 9944
http://trac.calendarserver.org//changeset/9944
Author: cdaboo at apple.com
Date: 2012-10-16 09:12:08 -0700 (Tue, 16 Oct 2012)
Log Message:
-----------
Implement apis to list all homes of a given type.
Modified Paths:
--------------
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/test/common.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -21,7 +21,7 @@
from StringIO import StringIO
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue, \
maybeDeferred
from twisted.internet.protocol import Protocol
from twisted.python import hashlib
@@ -427,7 +427,7 @@
def test_notificationSyncToken(self):
"""
L{ICalendar.resourceNamesSinceToken} will return the names of calendar
- objects changed or deleted since
+ objects changed or deleted since
"""
txn = self.transactionUnderTest()
coll = yield txn.notificationsWithUID("home1")
@@ -593,6 +593,23 @@
@inlineCallbacks
+ def test_calendarHomes(self):
+ """
+ Finding all existing calendar homes.
+ """
+ calendarHomes = (yield self.transactionUnderTest().calendarHomes())
+ self.assertEquals(
+ [home.name() for home in calendarHomes],
+ [
+ "home1",
+ "home_no_splits",
+ "home_splits",
+ "home_splits_shared",
+ ]
+ )
+
+
+ @inlineCallbacks
def test_displayNameNone(self):
"""
L{ICalendarHome.calendarWithName} returns C{None} for calendars which
@@ -684,6 +701,7 @@
self.assertProvides(ICalendar, calendar)
self.assertEquals(calendar.name(), name)
+
@inlineCallbacks
def test_calendarWithName_exists(self):
"""
@@ -780,7 +798,7 @@
"""
L{ICalendarHome.createCalendarWithName} raises
L{CalendarAlreadyExistsError} when the name conflicts with an already-
- existing
+ existing
"""
home = yield self.homeUnderTest()
for name in home1_calendarNames:
@@ -850,23 +868,25 @@
result = yield maybeDeferred(calendar.getSupportedComponents)
self.assertEquals(result, None)
+
@inlineCallbacks
def test_countComponentTypes(self):
"""
Test Calendar._countComponentTypes to make sure correct counts are returned.
"""
-
+
tests = (
("calendar_1", (("VEVENT", 3),)),
("calendar_2", (("VEVENT", 3), ("VTODO", 2))),
)
-
+
for calname, results in tests:
testalendar = yield (yield self.transactionUnderTest().calendarHomeWithUID(
"home_splits")).calendarWithName(calname)
result = yield maybeDeferred(testalendar._countComponentTypes)
self.assertEquals(result, results)
+
@inlineCallbacks
def test_calendarObjects(self):
"""
@@ -991,6 +1011,7 @@
]
)
+
@inlineCallbacks
def test_removeCalendarObjectWithName_exists(self):
"""
@@ -1088,6 +1109,7 @@
invitedCals = yield cal.asShared()
self.assertEqual(len(invitedCals), 0)
+
@inlineCallbacks
def test_unshareSharerSide(self, commit=False):
"""
@@ -1107,6 +1129,7 @@
invitedCals = yield cal.asShared()
self.assertEqual(len(invitedCals), 0)
+
@inlineCallbacks
def test_unshareShareeSide(self, commit=False):
"""
@@ -1126,6 +1149,7 @@
invitedCals = yield cal.asShared()
self.assertEqual(len(invitedCals), 0)
+
@inlineCallbacks
def test_unshareWithInDifferentTransaction(self):
"""
@@ -1174,7 +1198,7 @@
result = (yield home.hasCalendarResourceUIDSomewhereElse("uid2", object, "schedule"))
self.assertTrue(result)
-
+
# FIXME: do this without legacy calls
'''
from twistedcaldav.sharing import SharedCollectionRecord
@@ -1291,7 +1315,6 @@
self.assertEquals(component.mainType(), "VEVENT")
self.assertEquals(component.resourceUID(), "uid1")
-
perUserComponent = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
VERSION:2.0
@@ -1326,7 +1349,6 @@
END:VCALENDAR
""".replace("\n", "\r\n"))
-
asSeenByOwner = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
VERSION:2.0
@@ -1346,7 +1368,6 @@
END:VCALENDAR
""".replace("\n", "\r\n"))
-
asSeenByOther = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
VERSION:2.0
@@ -1464,6 +1485,7 @@
set(home1_calendarNames)
)
+
@inlineCallbacks
def test_loadAllCalendars(self):
"""
@@ -1485,7 +1507,7 @@
set(c.name() for c in calendars),
set(home1_calendarNames)
)
-
+
for c in calendars:
self.assertTrue(c.properties() is not None)
@@ -1570,6 +1592,7 @@
InvalidObjectResourceError,
)
+
@inlineCallbacks
def test_setComponent_invalid(self):
"""
@@ -1739,7 +1762,7 @@
self.assertNotEquals(event1_text, event1_text_withDifferentSubject)
newComponent = VComponent.fromString(event1_text_withDifferentSubject)
yield obj.setComponent(newComponent)
-
+
# Putting everything into a separate transaction to account for any
# caching that may take place.
yield self.commit()
@@ -1748,7 +1771,6 @@
propertyContent
)
-
eventWithDropbox = "\r\n".join("""
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
@@ -1857,7 +1879,7 @@
def test_collectionSyncToken(self):
"""
L{ICalendar.resourceNamesSinceToken} will return the names of calendar
- objects changed or deleted since
+ objects changed or deleted since
"""
cal = yield self.calendarUnderTest()
st = yield cal.syncToken()
@@ -2081,7 +2103,7 @@
that fails with L{QuotaExceeded}.
"""
home = yield self.homeUnderTest()
- attachment = yield getit()
+ attachment = yield getit()
t = attachment.store(MimeType("text", "x-fixture"))
sample = "all work and no play makes jack a dull boy"
chunk = (sample * (home.quotaAllowedBytes() / len(sample)))
@@ -2141,7 +2163,7 @@
yield checkOriginal()
- def test_removeAttachmentWithName(self, refresh=lambda x:x):
+ def test_removeAttachmentWithName(self, refresh=lambda x: x):
"""
L{ICalendarObject.removeAttachmentWithName} will remove the calendar
object with the given name.
@@ -2196,7 +2218,7 @@
@inlineCallbacks
def test_finishedOnCommit(self):
- """
+ """
Calling L{ITransaction.abort} or L{ITransaction.commit} after
L{ITransaction.commit} has already been called raises an
L{AlreadyFinishedError}.
@@ -2278,4 +2300,3 @@
additionalUIDs.add("home_attachments")
expectedUIDs = additionalUIDs.union(requiredUIDs)
self.assertEquals(foundUIDs, expectedUIDs)
-
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -67,7 +67,7 @@
calendarPath.parent().makedirs()
storePath.copyTo(calendarPath)
- # Set year values to current year
+ # Set year values to current year
nowYear = PyCalendarDateTime.getToday().getYear()
for home in calendarPath.child("ho").child("me").children():
if not home.basename().startswith("."):
@@ -75,7 +75,7 @@
if not calendar.basename().startswith("."):
for resource in calendar.children():
if resource.basename().endswith(".ics"):
- resource.setContent(resource.getContent() % {"now":nowYear})
+ resource.setContent(resource.getContent() % {"now": nowYear})
testID = test.id()
test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory,
@@ -314,8 +314,8 @@
self.calendar1.removeCalendarObjectWithName, name
)
+ counter = 0
- counter = 0
@inlineCallbacks
def _refresh(self):
"""
@@ -442,7 +442,7 @@
L{CalendarObject} has instance attributes, C{_path} and C{_calendar},
which refer to its position in the filesystem and the calendar in which
it is contained, respectively.
- """
+ """
self.failUnless(
isinstance(self.object1._path, FilePath),
self.object1._path
@@ -482,7 +482,6 @@
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
@@ -503,6 +502,25 @@
@inlineCallbacks
+ def test_calendarHomes(self):
+ """
+ Finding all existing calendar homes.
+ """
+ calendarHomes = (yield self.transactionUnderTest().calendarHomes())
+ self.assertEquals(
+ [home.name() for home in calendarHomes],
+ [
+ "home1",
+ "home_attachments",
+ "home_bad",
+ "home_no_splits",
+ "home_splits",
+ "home_splits_shared",
+ ]
+ )
+
+
+ @inlineCallbacks
def test_calendarObjectsWithDotFile(self):
"""
Adding a dotfile to the calendar home should not increase the number of
Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -72,6 +72,7 @@
self.limit = lowerLimit
+
class TimeRangeUpperLimit(Exception):
"""
A request for time-range information too far in the future cannot be satisfied.
@@ -81,11 +82,20 @@
self.limit = upperLimit
+
class ICalendarTransaction(ICommonTransaction):
"""
Transaction functionality required to be implemented by calendar stores.
"""
+ def calendarHomes():
+ """
+ Retrieve each calendar home in the store.
+
+ @return: a L{Deferred} which fires with a list of L{ICalendarHome}.
+ """
+
+
def calendarHomeWithUID(uid, create=False):
"""
Retrieve the calendar home for the principal with the given C{uid}.
@@ -115,6 +125,7 @@
"""
+
#
# Interfaces
#
@@ -134,6 +145,7 @@
@return: a string.
"""
+
def calendars():
"""
Retrieve calendars contained in this calendar home.
@@ -141,6 +153,7 @@
@return: an iterable of L{ICalendar}s.
"""
+
def loadCalendars():
"""
Pre-load all calendars Depth:1.
@@ -148,6 +161,7 @@
@return: an iterable of L{ICalendar}s.
"""
+
def calendarWithName(name):
"""
Retrieve the calendar with the given C{name} contained in this
@@ -185,6 +199,7 @@
given C{name} already exists.
"""
+
def removeCalendarWithName(name):
"""
Remove the calendar with the given C{name} from this calendar
@@ -288,6 +303,7 @@
@return: an L{ICalendarHome}.
"""
+
def calendarObjects():
"""
Retrieve the calendar objects contained in this calendar.
@@ -295,6 +311,7 @@
@return: an iterable of L{ICalendarObject}s.
"""
+
def calendarObjectWithName(name):
"""
Retrieve the calendar object with the given C{name} contained
@@ -305,6 +322,7 @@
object exists.
"""
+
def calendarObjectWithUID(uid):
"""
Retrieve the calendar object with the given C{uid} contained
@@ -316,6 +334,7 @@
such calendar object exists.
"""
+
def createCalendarObjectWithName(name, component):
"""
Create a calendar component with the given C{name} in this
@@ -333,6 +352,7 @@
a calendar object.
"""
+
def removeCalendarObjectWithName(name):
"""
Remove the calendar object with the given C{name} from this
@@ -343,6 +363,7 @@
exists.
"""
+
def removeCalendarObjectWithUID(uid):
"""
Remove the calendar object with the given C{uid} from this
@@ -353,6 +374,7 @@
not exist.
"""
+
def syncToken():
"""
Retrieve the current sync token for this calendar.
@@ -360,6 +382,7 @@
@return: a string containing a sync token.
"""
+
def calendarObjectsInTimeRange(start, end, timeZone):
"""
Retrieve all calendar objects in this calendar which have
@@ -372,6 +395,7 @@
@return: an iterable of L{ICalendarObject}s.
"""
+
def calendarObjectsSinceToken(token):
"""
Retrieve all calendar objects in this calendar that have
@@ -430,6 +454,8 @@
"""
# TODO: implement this for the file store.
+
+
class ICalendarObject(IDataStoreObject):
"""
Calendar object
@@ -444,6 +470,7 @@
@rtype: L{ICalendar}
"""
+
def setComponent(component):
"""
Rewrite this calendar object to match the given C{component}.
@@ -456,6 +483,7 @@
a calendar object.
"""
+
def component():
"""
Retrieve the calendar component for this calendar object.
@@ -467,6 +495,7 @@
@return: a C{VCALENDAR} L{VComponent}.
"""
+
def uid():
"""
Retrieve the UID for this calendar object.
@@ -474,6 +503,7 @@
@return: a string containing a UID.
"""
+
def componentType():
"""
Retrieve the iCalendar component type for the main component
@@ -497,6 +527,7 @@
@return: a URI string.
"""
+
def dropboxID():
"""
An identifier, unique to the calendar home, that specifies a location
@@ -629,5 +660,3 @@
that the stream is complete to its C{connectionLost} method.
@type protocol: L{IProtocol}
"""
-
-
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -234,6 +234,20 @@
@inlineCallbacks
+ def test_addressbookHomes(self):
+ """
+ Finding all existing addressbook homes.
+ """
+ addressbookHomes = (yield self.transactionUnderTest().addressbookHomes())
+ self.assertEquals(
+ [home.name() for home in addressbookHomes],
+ [
+ "home1",
+ ]
+ )
+
+
+ @inlineCallbacks
def test_addressbookHomeWithUID_exists(self):
"""
Finding an existing addressbook home by UID results in an object that
@@ -664,7 +678,7 @@
set(c.name() for c in addressbooks),
set(home1_addressbookNames)
)
-
+
for c in addressbooks:
self.assertTrue(c.properties() is not None)
@@ -910,7 +924,7 @@
self.assertNotEquals(vcard1_text, vcard1_text_withDifferentNote)
newComponent = VComponent.fromString(vcard1_text_withDifferentNote)
yield obj.setComponent(newComponent)
-
+
# Putting everything into a separate transaction to account for any
# caching that may take place.
yield self.commit()
@@ -979,6 +993,3 @@
additionalUIDs.add("home_bad")
expectedUIDs = additionalUIDs.union(requiredUIDs)
self.assertEquals(foundUIDs, expectedUIDs)
-
-
-
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -434,7 +434,7 @@
L{AddressBookObject} has instance attributes, C{_path} and C{_addressbook},
which refer to its position in the filesystem and the addressbook in which
it is contained, respectively.
- """
+ """
self.failUnless(
isinstance(self.object1._path, FilePath),
self.object1._path
@@ -445,6 +445,7 @@
)
+
class FileStorageTests(CommonTests, unittest.TestCase):
"""
File storage tests.
@@ -471,6 +472,21 @@
@inlineCallbacks
+ def test_addressbookHomes(self):
+ """
+ Finding all existing addressbook homes.
+ """
+ addressbookHomes = (yield self.transactionUnderTest().addressbookHomes())
+ self.assertEquals(
+ [home.name() for home in addressbookHomes],
+ [
+ "home1",
+ "home_bad",
+ ]
+ )
+
+
+ @inlineCallbacks
def test_addressbookObjectsWithDotFile(self):
"""
Adding a dotfile to the addressbook home should not create a new
@@ -489,5 +505,3 @@
((yield self.addressbookUnderTest())._path.child("not-a-vcard")
.createDirectory())
yield self.test_addressbookObjects()
-
-
Modified: CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/iaddressbookstore.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/carddav/iaddressbookstore.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -19,7 +19,7 @@
Address book store interfaces
"""
-from txdav.common.icommondatastore import ICommonTransaction,\
+from txdav.common.icommondatastore import ICommonTransaction, \
IShareableCollection
from txdav.idav import INotifier
from txdav.idav import IDataStoreObject
@@ -37,6 +37,14 @@
Transaction interface that addressbook stores must provide.
"""
+ def addressbookHomes():
+ """
+ Retrieve each addressbook home in the store.
+
+ @return: a L{Deferred} which fires with a list of L{ICalendarHome}.
+ """
+
+
def addressbookHomeWithUID(uid, create=False):
"""
Retrieve the addressbook home for the principal with the given C{uid}.
@@ -79,6 +87,7 @@
@return: an iterable of L{IAddressBook}s.
"""
+
def loadAddressbooks():
"""
Pre-load all addressbooks Depth:1.
@@ -86,6 +95,7 @@
@return: an iterable of L{IAddressBook}s.
"""
+
def addressbookWithName(name):
"""
Retrieve the addressbook with the given C{name} contained in this
@@ -96,6 +106,7 @@
exists.
"""
+
def createAddressBookWithName(name):
"""
Create an addressbook with the given C{name} in this addressbook
@@ -106,6 +117,7 @@
given C{name} already exists.
"""
+
def removeAddressBookWithName(name):
"""
Remove the addressbook with the given C{name} from this addressbook
@@ -117,6 +129,7 @@
"""
+
class IAddressBook(INotifier, IShareableCollection, IDataStoreObject):
"""
AddressBook
@@ -132,6 +145,7 @@
Change the name of this addressbook.
"""
+
def ownerAddressBookHome():
"""
Retrieve the addressbook home for the owner of this addressbook.
@@ -141,6 +155,7 @@
@return: an L{IAddressBookHome}.
"""
+
def addressbookObjects():
"""
Retrieve the addressbook objects contained in this addressbook.
@@ -148,13 +163,14 @@
@return: an iterable of L{IAddressBookObject}s.
"""
+
def addressbookObjectWithName(name):
"""
Retrieve the addressbook object with the given C{name} contained
in this addressbook.
@param name: a string.
-
+
@return: a L{Deferred} that fires with an L{IAddressBookObject} or
C{None} if no such addressbook object exists.
"""
@@ -169,6 +185,7 @@
object exists.
"""
+
def createAddressBookObjectWithName(name, component):
"""
Create an addressbook component with the given C{name} in this
@@ -186,6 +203,7 @@
an addressbook object.
"""
+
def removeAddressBookObjectWithName(name):
"""
Remove the addressbook object with the given C{name} from this
@@ -196,6 +214,7 @@
exists.
"""
+
def removeAddressBookObjectWithUID(uid):
"""
Remove the addressbook object with the given C{uid} from this
@@ -206,6 +225,7 @@
not exist.
"""
+
def syncToken():
"""
Retrieve the current sync token for this addressbook.
@@ -213,6 +233,7 @@
@return: a string containing a sync token.
"""
+
def addressbookObjectsSinceToken(token):
"""
Retrieve all addressbook objects in this addressbook that have
@@ -225,6 +246,7 @@
"""
+
class IAddressBookObject(IDataStoreObject):
"""
AddressBook object
@@ -250,6 +272,7 @@
an addressbook object.
"""
+
def component():
"""
Retrieve the addressbook component for this addressbook object.
@@ -261,6 +284,7 @@
@return: a C{VCARD} L{VComponent}.
"""
+
def uid():
"""
Retrieve the UID for this addressbook object.
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -24,7 +24,7 @@
from txdav.xml.rfc2518 import ResourceType, GETContentType, HRef
from txdav.xml.rfc5842 import ResourceID
from twext.web2.http_headers import generateContentType, MimeType
-from twext.web2.dav.resource import TwistedGETContentMD5,\
+from twext.web2.dav.resource import TwistedGETContentMD5, \
TwistedQuotaUsedProperty
from twisted.internet.defer import succeed, inlineCallbacks, returnValue
@@ -139,6 +139,7 @@
self._migrating = state
self._enableNotifications = not state
+
def setUpgrading(self, state):
"""
Set the "upgrading" state
@@ -179,6 +180,7 @@
return self._homesOfType(EADDRESSBOOKTYPE)
+
class CommonStoreTransaction(DataStoreTransaction):
"""
In-memory implementation of
@@ -226,14 +228,45 @@
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
- @memoizedKey('uid', '_calendarHomes')
+ def calendarHomes(self):
+ return self.homes(ECALENDARTYPE)
+
+
def calendarHomeWithUID(self, uid, create=False):
return self.homeWithUID(ECALENDARTYPE, uid, create=create)
- @memoizedKey("uid", "_addressbookHomes")
+
+ def addressbookHomes(self):
+ return self.homes(EADDRESSBOOKTYPE)
+
+
def addressbookHomeWithUID(self, uid, create=False):
return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
+
+ def _determineMemo(self, storeType, uid, create=False):
+ """
+ Determine the memo dictionary to use for homeWithUID.
+ """
+ if storeType == ECALENDARTYPE:
+ return self._calendarHomes
+ else:
+ return self._addressbookHomes
+
+
+ def homes(self, storeType):
+ """
+ Load all calendar or addressbook homes.
+ """
+ uids = self._homeClass[storeType].listHomes(self)
+ for uid in uids:
+ self.homeWithUID(storeType, uid, create=False)
+
+ # Return the memoized list directly
+ returnValue([kv[1] for kv in sorted(self._determineMemo(storeType, None).items(), key=lambda x: x[0])])
+
+
+ @memoizedKey("uid", _determineMemo)
def homeWithUID(self, storeType, uid, create=False):
if uid.startswith("."):
return None
@@ -243,6 +276,7 @@
return self._homeClass[storeType].homeWithUID(self, uid, create, storeType == ECALENDARTYPE)
+
@memoizedKey("uid", "_notificationHomes")
def notificationsWithUID(self, uid, home=None):
@@ -255,27 +289,35 @@
def addAPNSubscription(self, token, key, timestamp, subscriber, userAgent, ipAddr):
return NotImplementedError
+
def removeAPNSubscription(self, token, key):
return NotImplementedError
+
def purgeOldAPNSubscriptions(self, purgeSeconds):
return NotImplementedError
+
def apnSubscriptionsByToken(self, token):
return NotImplementedError
+
def apnSubscriptionsByKey(self, key):
return NotImplementedError
+
def apnSubscriptionsBySubscriber(self, guid):
return NotImplementedError
+
def isNotifiedAlready(self, obj):
return obj in self._notifiedAlready
+
def notificationAddedForObject(self, obj):
self._notifiedAlready.add(obj)
+
def isBumpedAlready(self, obj):
"""
Indicates whether or not bumpAddedForObject has already been
@@ -284,6 +326,7 @@
"""
return obj in self._bumpedAlready
+
def bumpAddedForObject(self, obj):
"""
Records the fact that a bumpModified( ) call has already been
@@ -332,6 +375,31 @@
@classmethod
+ def listHomes(cls, txn):
+ """
+ Retrieve the owner UIDs of all existing homes.
+
+ @return: an iterable of C{str}s.
+ """
+ results = []
+ top = txn._dataStore._path.child(cls._topPath)
+ if top.exists() and top.isdir() and top.child(UIDPATH).exists():
+ for firstPrefix in top.child(UIDPATH).children():
+ if not isValidName(firstPrefix.basename()):
+ continue
+ for secondPrefix in firstPrefix.children():
+ if not isValidName(secondPrefix.basename()):
+ continue
+ for actualHome in secondPrefix.children():
+ uid = actualHome.basename()
+ if not isValidName(uid):
+ continue
+ results.append(uid)
+
+ return results
+
+
+ @classmethod
def homeWithUID(cls, txn, uid, create=False, withNotifications=False):
assert len(uid) >= 4
@@ -391,6 +459,7 @@
return home
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._path)
@@ -402,6 +471,7 @@
def transaction(self):
return self._transaction
+
def retrieveOldShares(self):
"""
Retrieve the old Index object.
@@ -434,7 +504,7 @@
) | set(
name
for name in self._path.listdir()
- if not name.startswith(".") and
+ if not name.startswith(".") and
self._path.child(name).isdir() and
name not in self._removedChildren
))
@@ -516,6 +586,7 @@
self.notifyChanged()
return c
+
@writeOperation
def removeChildWithName(self, name):
if name.startswith(".") or name in self._removedChildren:
@@ -533,13 +604,14 @@
else:
self._removedChildren.add(name)
+
@inlineCallbacks
def syncToken(self):
-
+
maxrev = 0
for child in self.children():
maxrev = max(int((yield child.syncToken()).split("_")[1]), maxrev)
-
+
try:
urnuuid = str(self.properties()[PropertyName.fromElement(ResourceID)].children[0])
except KeyError:
@@ -565,6 +637,7 @@
self._transaction.addOperation(props.flush, "flush home properties")
return props
+
def objectResourcesWithUID(self, uid, ignore_children=()):
"""
Return all child object resources with the specified UID, ignoring any in the
@@ -579,6 +652,7 @@
results.append(object)
return results
+
def quotaUsedBytes(self):
try:
@@ -586,31 +660,34 @@
except KeyError:
return 0
+
def adjustQuotaUsedBytes(self, delta):
"""
Adjust quota used. We need to get a lock on the row first so that the adjustment
is done atomically.
"""
-
+
old_used = self.quotaUsedBytes()
new_used = old_used + delta
if new_used < 0:
self.log_error("Fixing quota adjusted below zero to %s by change amount %s" % (new_used, delta,))
new_used = 0
self.properties()[PropertyName.fromElement(TwistedQuotaUsedProperty)] = TwistedQuotaUsedProperty(str(new_used))
-
+
def addNotifier(self, notifier):
if self._notifiers is None:
self._notifiers = ()
self._notifiers += (notifier,)
-
+
+
def notifierID(self, label="default"):
if self._notifiers:
return self._notifiers[0].getID(label)
else:
return None
+
@inlineCallbacks
def nodeName(self, label="default"):
if self._notifiers:
@@ -621,6 +698,7 @@
else:
returnValue(None)
+
def notifyChanged(self):
"""
Trigger a notification of a change
@@ -633,6 +711,7 @@
self._transaction.notificationAddedForObject(self)
+
class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin, HomeChildBase):
"""
Common ancestor class of AddressBooks and Calendars.
@@ -684,6 +763,7 @@
def objectWithName(cls, home, name, owned):
return cls(name, home, owned) if home._path.child(name).isdir() else None
+
@property
def _path(self):
return self._home._path.child(self._name)
@@ -692,12 +772,14 @@
def resourceType(self):
return NotImplementedError
+
def retrieveOldIndex(self):
"""
Retrieve the old Index object.
"""
return self._index._oldIndex
+
def retrieveOldInvites(self):
"""
Retrieve the old Invites DB object.
@@ -722,6 +804,7 @@
"""
return BIND_OWN
+
def owned(self):
return self._owned
@@ -743,6 +826,7 @@
self.notifyChanged()
+
@writeOperation
def remove(self):
@@ -856,7 +940,7 @@
rname = self.retrieveOldIndex().resourceNameForUID(uid)
if rname and rname not in self._removedObjectResources:
return self.objectResourceWithName(rname)
-
+
return None
@@ -864,7 +948,7 @@
def createObjectResourceWithName(self, name, component, metadata=None):
"""
Create a new resource with component data and optional metadata. We create the
- python object using the metadata then create the actual store object with setComponent.
+ python object using the metadata then create the actual store object with setComponent.
"""
if name.startswith("."):
raise ObjectResourceNameNotAllowedError(name)
@@ -935,6 +1019,7 @@
"""
return True
+
# FIXME: property writes should be a write operation
@cached
def properties(self):
@@ -958,17 +1043,20 @@
"""
pass
+
def addNotifier(self, notifier):
if self._notifiers is None:
self._notifiers = ()
self._notifiers += (notifier,)
-
+
+
def notifierID(self, label="default"):
if self._notifiers:
return self._notifiers[0].getID(label)
else:
return None
+
@inlineCallbacks
def nodeName(self, label="default"):
if self._notifiers:
@@ -979,6 +1067,7 @@
else:
returnValue(None)
+
def notifyChanged(self):
"""
Trigger a notification of a change
@@ -991,6 +1080,7 @@
self._transaction.notificationAddedForObject(self)
+
class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
"""
@ivar _path: The path of the file on disk
@@ -1018,9 +1108,11 @@
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._path.path)
+
def transaction(self):
return self._transaction
+
@writeOperation
def setComponent(self, component, inserting=False):
raise NotImplementedError
@@ -1037,6 +1129,7 @@
def uid(self):
raise NotImplementedError
+
@cached
def properties(self):
home = self._parentCollection._home
@@ -1050,6 +1143,7 @@
self._transaction.addOperation(props.flush, "object properties flush")
return props
+
def initPropertyStore(self, props):
"""
A hook for subclasses to override in order to set up their property
@@ -1059,6 +1153,8 @@
"""
pass
+
+
class CommonStubResource(object):
"""
Just enough resource to keep the collection sql DB classes going.
@@ -1067,6 +1163,7 @@
self.resource = resource
self.fp = self.resource._path
+
def bumpSyncToken(self, reset=False):
# FIXME: needs direct tests
return self.resource._updateSyncToken(reset)
@@ -1077,6 +1174,7 @@
self.bumpSyncToken(True)
+
class NotificationCollection(CommonHomeChild):
"""
File-based implementation of L{INotificationCollection}.
@@ -1101,6 +1199,7 @@
self._invites = None
self._objectResourceClass = NotificationObject
+
@classmethod
def notificationsFromHome(cls, txn, home):
@@ -1144,6 +1243,7 @@
props[PropertyName(*ResourceType.qname())] = c.resourceType()
return c
+
def resourceType(self):
return ResourceType.notification #@UndefinedVariable
@@ -1156,6 +1256,7 @@
name = uid + ".xml"
return self.notificationObjectWithName(name)
+
def writeNotificationObject(self, uid, xmltype, xmldata):
name = uid + ".xml"
if name.startswith("."):
@@ -1167,9 +1268,10 @@
# Update database
self.retrieveOldIndex().addOrUpdateRecord(NotificationRecord(uid, name, xmltype.name))
-
+
self.notifyChanged()
+
@writeOperation
def removeNotificationObjectWithName(self, name):
if name.startswith("."):
@@ -1186,17 +1288,19 @@
return lambda: None
self._transaction.addOperation(do, "remove object resource object %r" %
(name,))
-
+
self.notifyChanged()
else:
raise NoSuchObjectResourceError(name)
+
@writeOperation
def removeNotificationObjectWithUID(self, uid):
name = uid + ".xml"
self.removeNotificationObjectWithName(name)
+
class NotificationObject(CommonObjectResource):
"""
"""
@@ -1206,6 +1310,7 @@
super(NotificationObject, self).__init__(name, notifications)
self._uid = name[:-4]
+
def notificationCollection(self):
return self._parentCollection
@@ -1216,6 +1321,7 @@
return int(reactor.seconds())
return super(NotificationObject, self).created()
+
def modified(self):
if not self._path.exists():
from twisted.internet import reactor
@@ -1261,11 +1367,10 @@
self.properties().update(self.properties())
props = self.properties()
- props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"})))
+ props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset": "utf-8"})))
props[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
props[PropertyName.fromElement(TwistedGETContentMD5)] = TwistedGETContentMD5.fromString(md5)
-
# FIXME: the property store's flush() method may already have been
# added to the transaction, but we need to add it again to make sure it
# happens _after_ the new file has been written. we may end up doing
@@ -1273,7 +1378,6 @@
# manipulation methods won't work.
self._transaction.addOperation(self.properties().flush, "post-update property flush")
-
_xmldata = None
def xmldata(self):
@@ -1298,10 +1402,12 @@
def uid(self):
return self._uid
+
def xmlType(self):
# NB This is the NotificationType property element
return self.properties()[PropertyName.fromElement(NotificationType)]
+
def initPropertyStore(self, props):
# Setup peruser special properties
props.setSpecialProperties(
@@ -1313,6 +1419,7 @@
)
+
class NotificationIndex(object):
#
# OK, here's where we get ugly.
@@ -1322,4 +1429,3 @@
self.notificationCollection = notificationCollection
stubResource = CommonStubResource(notificationCollection)
self._oldIndex = OldNotificationIndex(stubResource)
-
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2012-10-16 00:34:32 UTC (rev 9943)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2012-10-16 16:12:08 UTC (rev 9944)
@@ -465,10 +465,18 @@
raise RuntimeError("Database key %s cannot be determined." % (key,))
+ def calendarHomes(self):
+ return self.homes(ECALENDARTYPE)
+
+
def calendarHomeWithUID(self, uid, create=False):
return self.homeWithUID(ECALENDARTYPE, uid, create=create)
+ def addressbookHomes(self):
+ return self.homes(EADDRESSBOOKTYPE)
+
+
def addressbookHomeWithUID(self, uid, create=False):
return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
@@ -483,6 +491,21 @@
return self._addressbookHomes
+ @inlineCallbacks
+ def homes(self, storeType):
+ """
+ Load all calendar or addressbook homes.
+ """
+
+ # Get all UIDs and load them - this will memoize all existing ones
+ uids = (yield self._homeClass[storeType].listHomes(self))
+ for uid in uids:
+ yield self.homeWithUID(storeType, uid, create=False)
+
+ # Return the memoized list directly
+ returnValue([kv[1] for kv in sorted(self._determineMemo(storeType, None).items(), key=lambda x: x[0])])
+
+
@memoizedKey("uid", _determineMemo)
def homeWithUID(self, storeType, uid, create=False):
if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
@@ -1103,6 +1126,22 @@
@classmethod
@inlineCallbacks
+ def listHomes(cls, txn):
+ """
+ Retrieve the owner UIDs of all existing homes.
+
+ @return: an iterable of C{str}s.
+ """
+ rows = yield Select(
+ [cls._homeSchema.OWNER_UID],
+ From=cls._homeSchema,
+ ).on(txn)
+ rids = [row[0] for row in rows]
+ returnValue(rids)
+
+
+ @classmethod
+ @inlineCallbacks
def homeWithUID(cls, txn, uid, create=False):
if txn._notifierFactory:
notifiers = (txn._notifierFactory.newNotifier(
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121016/60c687b5/attachment-0001.html>
More information about the calendarserver-changes
mailing list