[CalendarServer-changes] [9884] CalendarServer/branches/users/cdaboo/ischedule-dkim
source_changes at macosforge.org
source_changes at macosforge.org
Mon Oct 1 05:44:46 PDT 2012
Revision: 9884
http://trac.calendarserver.org//changeset/9884
Author: cdaboo at apple.com
Date: 2012-10-01 05:44:46 -0700 (Mon, 01 Oct 2012)
Log Message:
-----------
Merge from trunk.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/test/test_caldav.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/purge.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/test/test_purge.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/validcalendardata.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/conf/caldavd-test.plist
CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/tools/readStats.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/lib-patches/cx_Oracle/nclob-fixes-and-prefetch.patch
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/cdaboo/ischedule-dkim/support/Makefile.Apple
CalendarServer/branches/users/cdaboo/ischedule-dkim/support/submit
CalendarServer/branches/users/cdaboo/ischedule-dkim/support/version.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/syntax.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/test/test_sqlsyntax.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/internet/tcp.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/channel/http.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/metafd.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/test/test_http.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/directory/util.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/localization.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/put_common.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/resource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/scheduler.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_resource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/scheduler.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/scheduler.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharing.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_link.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_localization.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_resource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_sharing.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/base.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/test/test_sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/file.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/util.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql_legacy.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/icommondatastore.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po
Removed Paths:
-------------
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/English.lproj/
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.mo
CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/fr/
CalendarServer/branches/users/cdaboo/ischedule-dkim/support/pygettext.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharedcollection.py
Property Changed:
----------------
CalendarServer/branches/users/cdaboo/ischedule-dkim/
Property changes on: CalendarServer/branches/users/cdaboo/ischedule-dkim
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9747-9851
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9747-9883
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -41,7 +41,7 @@
from twisted.python.logfile import LogFile
from twisted.python.usage import Options, UsageError
-from twisted.internet.defer import gatherResults, Deferred
+from twisted.internet.defer import gatherResults, Deferred, inlineCallbacks
from twisted.internet.process import ProcessExitedAlready
from twisted.internet.protocol import Protocol, Factory
@@ -187,6 +187,7 @@
optionalVars = [
"KRB5_KTNAME",
"ORACLE_HOME",
+ "VERSIONER_PYTHON_PREFER_32_BIT",
]
for varname in requiredVars:
@@ -218,7 +219,7 @@
-class ErrorLoggingMultiService(MultiService):
+class ErrorLoggingMultiService(MultiService, object):
""" Registers a rotating file logger for error logging, if
config.ErrorLogEnabled is True. """
@@ -241,6 +242,11 @@
class CalDAVService (ErrorLoggingMultiService):
+ # The ConnectionService is a MultiService which bundles all the connection
+ # services together for the purposes of being able to stop them and wait
+ # for all of their connections to close before shutting down.
+ connectionServiceName = "ConnectionService"
+
def __init__(self, logObserver):
self.logObserver = logObserver # accesslog observer
MultiService.__init__(self)
@@ -251,10 +257,18 @@
self.logObserver.start()
+ @inlineCallbacks
def stopService(self):
- d = MultiService.stopService(self)
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ connectionService = self.getServiceNamed(self.connectionServiceName)
+ # Note: removeService() also calls stopService()
+ yield self.removeService(connectionService)
+ # At this point, all outstanding requests have been responded to
+ yield super(CalDAVService, self).stopService()
self.logObserver.stop()
- return d
@@ -345,23 +359,22 @@
def postOptions(self):
- self.loadConfiguration()
- self.checkConfiguration()
+ try:
+ self.loadConfiguration()
+ self.checkConfiguration()
+ except ConfigurationError, e:
+ print "Invalid configuration: %s" % (e,)
+ sys.exit(1)
def loadConfiguration(self):
if not os.path.exists(self["config"]):
- print "Config file %s not found. Exiting." % (self["config"],)
- sys.exit(1)
+ raise ConfigurationError("Config file %s not found. Exiting."
+ % (self["config"],))
print "Reading configuration from file: %s" % (self["config"],)
- try:
- config.load(self["config"])
- except ConfigurationError, e:
- print "Invalid configuration: %s" % (e,)
- sys.exit(1)
-
+ config.load(self["config"])
config.updateDefaults(self.overrides)
@@ -874,6 +887,12 @@
config.addPostUpdateHooks((updateFactory,))
+ # Bundle the various connection services within a single MultiService
+ # that can be stopped before the others for graceful shutdown.
+ connectionService = MultiService()
+ connectionService.setName(CalDAVService.connectionServiceName)
+ connectionService.setServiceParent(service)
+
if config.InheritFDs or config.InheritSSLFDs:
# Inherit sockets to call accept() on them individually.
@@ -889,13 +908,13 @@
contextFactory,
backlog=config.ListenBacklog,
inherit=True
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
for fdAsStr in config.InheritFDs:
MaxAcceptTCPServer(
int(fdAsStr), httpFactory,
backlog=config.ListenBacklog,
inherit=True
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
elif config.MetaFD:
# Inherit a single socket to receive accept()ed connections via
@@ -912,7 +931,7 @@
ReportingHTTPService(
requestFactory, int(config.MetaFD), contextFactory
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
else: # Not inheriting, therefore we open our own:
for bindAddress in self._allBindAddresses():
@@ -935,7 +954,7 @@
backlog=config.ListenBacklog,
inherit=False
)
- httpsService.setServiceParent(service)
+ httpsService.setServiceParent(connectionService)
for port in config.BindHTTPPorts:
MaxAcceptTCPServer(
@@ -943,7 +962,7 @@
interface=bindAddress,
backlog=config.ListenBacklog,
inherit=False
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
# Change log level back to what it was before
setLogLevelForNamespace(None, oldLogLevel)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/test/test_caldav.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/test/test_caldav.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -542,7 +542,8 @@
Test that the Slave service has sub services with the
default TCP and SSL configuration
"""
- service = self.makeService()
+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
expectedSubServices = dict((
(MaxAcceptTCPServer, self.config["HTTPPort"]),
@@ -568,7 +569,7 @@
Test that the configuration of the SSLServer reflect the config file's
SSL Private Key and SSL Certificate
"""
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
sslService = None
for s in service.services:
@@ -597,7 +598,7 @@
del self.config["SSLPort"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
self.assertNotIn(
internet.SSLServer,
@@ -612,7 +613,7 @@
del self.config["HTTPPort"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
self.assertNotIn(
internet.TCPServer,
@@ -626,7 +627,7 @@
self.config.BindAddresses = ["127.0.0.1"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
for s in service.services:
if isinstance(s, (internet.TCPServer, internet.SSLServer)):
@@ -644,7 +645,7 @@
]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
tcpServers = []
sslServers = []
@@ -676,7 +677,7 @@
"""
self.config.ListenBacklog = 1024
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
for s in service.services:
if isinstance(s, (internet.TCPServer, internet.SSLServer)):
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/purge.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/purge.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -666,19 +666,7 @@
# If in "completely" mode, unshare collections, remove notifications
if calHomeProvisioned and completely:
- # Process shared-to-me calendars
- names = list((yield storeCalHome.listSharedChildren()))
- for name in names:
- if verbose:
- if dryrun:
- print "Would unshare: %s" % (name,)
- else:
- print "Unsharing: %s" % (name,)
- if not dryrun:
- child = (yield storeCalHome.sharedChildWithName(name))
- (yield child.unshare())
-
- # Process shared calendars
+ # Process shared and shared-to-me calendars
children = list((yield storeCalHome.children()))
for child in children:
if verbose:
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/test/test_purge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/test/test_purge.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/test/test_purge.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -847,10 +847,10 @@
txn = self._sqlCalendarStore.newTransaction()
home = (yield txn.calendarHomeWithUID(self.uid))
- calendar2 = (yield home.sharedChildWithName(self.sharedName))
+ calendar2 = (yield home.childWithName(self.sharedName))
self.assertNotEquals(calendar2, None)
home2 = (yield txn.calendarHomeWithUID(self.uid2))
- calendar1 = (yield home2.sharedChildWithName(self.sharedName2))
+ calendar1 = (yield home2.childWithName(self.sharedName2))
self.assertNotEquals(calendar1, None)
(yield txn.commit())
@@ -890,7 +890,7 @@
self.assertEquals(home, None)
# Verify calendar1 was unshared to uid2
home2 = (yield txn.calendarHomeWithUID(self.uid2))
- self.assertEquals((yield home2.sharedChildWithName(self.sharedName)), None)
+ self.assertEquals((yield home2.childWithName(self.sharedName)), None)
(yield txn.commit())
count, ignored = (yield purgeUID(self.storeUnderTest(), self.uid, self.directory,
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/validcalendardata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/validcalendardata.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tools/validcalendardata.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -17,7 +17,7 @@
"""
This tool takes data from stdin and validates it as iCalendar data suitable
-for the server.
+for the server.
"""
from calendarserver.tools.cmdline import utilityMain
@@ -25,7 +25,7 @@
from twisted.python.text import wordWrap
from twisted.python.usage import Options
from twistedcaldav.config import config
-from twistedcaldav.ical import Component
+from twistedcaldav.ical import Component, InvalidICalendarDataError
from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
import os
import sys
@@ -62,6 +62,7 @@
optFlags = [
['verbose', 'v', "Verbose logging."],
+ ['parse-only', 'p', "Only validate parsing of the data."],
]
optParameters = [
@@ -91,8 +92,9 @@
if self.outputName == '-':
return sys.stdout
else:
- return open(self.outputName, 'wb')
+ return open(self.outputName, "wb")
+
def opt_input(self, filename):
"""
Specify output file path (default: '-', meaning stdin).
@@ -109,7 +111,7 @@
if self.inputName == '-':
return sys.stdin
else:
- return open(os.path.expanduser(self.inputName), 'rb')
+ return open(os.path.expanduser(self.inputName), "rb")
@@ -122,10 +124,10 @@
def __init__(self, store, options, output, input, reactor, config):
super(ValidService, self).__init__()
- self.store = store
+ self.store = store
self.options = options
- self.output = output
- self.input = input
+ self.output = output
+ self.input = input
self.reactor = reactor
self.config = config
self._directory = None
@@ -136,8 +138,11 @@
Start the service.
"""
super(ValidService, self).startService()
- result, message = self.validCalendarData()
-
+ if self.options["parse-only"]:
+ result, message = self.parseCalendarData()
+ else:
+ result, message = self.validCalendarData()
+
if result:
print "Calendar data OK"
else:
@@ -145,11 +150,38 @@
self.reactor.stop()
+ def parseCalendarData(self):
+ """
+ Check the calendar data for valid iCalendar data.
+ """
+
+ result = True
+ message = ""
+ try:
+ component = Component.fromString(self.input.read())
+
+ # Do underlying iCalendar library validation with data fix
+ fixed, unfixed = component._pycalendar.validate(doFix=True)
+
+ if unfixed:
+ raise InvalidICalendarDataError("Calendar data had unfixable problems:\n %s" % ("\n ".join(unfixed),))
+ if fixed:
+ print "Calendar data had fixable problems:\n %s" % ("\n ".join(fixed),)
+
+ except ValueError, e:
+ result = False
+ message = str(e)
+ if message.startswith(errorPrefix):
+ message = message[len(errorPrefix):]
+
+ return (result, message,)
+
+
def validCalendarData(self):
"""
Check the calendar data for valid iCalendar data.
"""
-
+
result = True
message = ""
truncated = False
@@ -167,10 +199,11 @@
message = message[len(errorPrefix):]
if truncated:
message = "Calendar data RRULE truncated\n" + message
-
+
return (result, message,)
+
def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
"""
Do the export.
@@ -189,9 +222,14 @@
except IOError, e:
stderr.write("Unable to open input file for reading: %s\n" % (e))
sys.exit(1)
+
+
def makeService(store):
return ValidService(store, options, output, input, reactor, config)
- utilityMain(options['config'], makeService, reactor)
-if __name__ == '__main__':
+ utilityMain(options["config"], makeService, reactor)
+
+
+
+if __name__ == "__main__":
main()
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/conf/caldavd-test.plist 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/conf/caldavd-test.plist 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1012,7 +1012,7 @@
<key>LocalesDirectory</key>
<string>locales</string>
<key>Language</key>
- <string>English</string>
+ <string>en</string>
</dict>
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/tools/readStats.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/tools/readStats.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/contrib/tools/readStats.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -30,6 +30,8 @@
def safeDivision(value, total, factor=1):
return value * factor / total if total else 0
+
+
def readSock(sockname, useTCP):
try:
s = socket.socket(socket.AF_INET if useTCP else socket.AF_UNIX, socket.SOCK_STREAM)
@@ -48,22 +50,26 @@
data["Server"] = sockname
return data
+
+
def printStats(stats):
- if len(stats) == 1 and False:
+ if len(stats) == 1:
if "Failed" in stats[0]:
- printFailedStats(stats[0]["Failed"])
+ printFailedStats(stats[0]["Failed"])
else:
try:
printStat(stats[0])
except KeyError, e:
printFailedStats("Unable to find key '%s' in statistics from server socket" % (e,))
sys.exit(1)
-
+
else:
printMultipleStats(stats)
-
+
+
+
def printStat(stats):
-
+
print "- " * 40
print "Server: %s" % (stats["Server"],)
print datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
@@ -85,10 +91,12 @@
printRequestSummary(stats)
printHistogramSummary(stats["5 Minutes"])
+
+
def printMultipleStats(stats):
labels = serverLabels(stats)
-
+
print "- " * 40
print "Servers: %s" % (", ".join(labels),)
@@ -115,30 +123,36 @@
print "Current CPU: %s" % (", ".join(cpus),)
print "Current Memory Used: %s" % (", ".join(memories),)
print
- printMultiRequestSummary(stats, labels, ("5 Minutes", 5*60,))
+ printMultiRequestSummary(stats, labels, ("5 Minutes", 5 * 60,))
printMultiHistogramSummary(stats, "5 Minutes")
+
+
def serverLabels(stats):
return [str(stat["Server"]) for stat in stats]
+
+
def printFailedStats(message):
-
+
print "- " * 40
print datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
print message
print
+
+
def printRequestSummary(stats):
table = tables.Table()
table.addHeader(
- ("Period", "Requests", "Av. Requests", "Av. Response", "Av. Response", "Max. Response", "Slot", "CPU", "500's"),
+ ("Period", "Requests", "Av. Requests", "Av. Response", "Av. Response", "Max. Response", "Slot", "CPU", "500's"),
)
table.addHeader(
- ( "", "", "per second", "(ms)", "no write(ms)", "(ms)", "Average", "Average", ""),
+ ("", "", "per second", "(ms)", "no write(ms)", "(ms)", "Average", "Average", ""),
)
table.setDefaultColumnFormats(
(
- tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY),
+ tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY),
tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
@@ -149,9 +163,9 @@
tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
)
)
-
- for key, seconds in (("Current", 60,), ("1 Minute", 60,), ("5 Minutes", 5*60,), ("1 Hour", 60*60,),):
+ for key, seconds in (("Current", 60,), ("1 Minute", 60,), ("5 Minutes", 5 * 60,), ("1 Hour", 60 * 60,),):
+
stat = stats[key]
table.addRow((
key,
@@ -164,22 +178,24 @@
safeDivision(stat["cpu"], stat["requests"]),
stat["500"],
))
-
+
os = StringIO()
table.printTable(os=os)
print os.getvalue()
+
+
def printMultiRequestSummary(stats, labels, index):
table = tables.Table()
table.addHeader(
- ("Server", "Requests", "Av. Requests", "Av. Response", "Av. Response", "Max. Response", "Slot", "CPU", "500's"),
+ ("Server", "Requests", "Av. Requests", "Av. Response", "Av. Response", "Max. Response", "Slot", "CPU", "500's"),
)
table.addHeader(
- ( "", "", "per second", "(ms)", "no write(ms)", "(ms)", "Average", "Average", ""),
+ ("", "", "per second", "(ms)", "no write(ms)", "(ms)", "Average", "Average", ""),
)
table.setDefaultColumnFormats(
(
- tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY),
+ tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY),
tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%.1f", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
@@ -190,13 +206,13 @@
tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
)
)
-
+
key, seconds = index
totals = ["Overall:", 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0]
for ctr, stat in enumerate(stats):
stat = stat[key]
-
+
col = []
col.append(labels[ctr])
col.append(stat["requests"])
@@ -210,26 +226,28 @@
table.addRow(col)
for item in xrange(1, len(col)):
totals[item] += col[item]
-
+
for item in (2, 3, 4, 6, 7):
totals[item] /= len(stats)
-
+
table.addFooter(totals)
os = StringIO()
table.printTable(os=os)
print os.getvalue()
+
+
def printHistogramSummary(stat):
-
+
print "5 minute average response histogram"
table = tables.Table()
table.addHeader(
- ("", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s"),
+ ("", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s"),
)
table.setDefaultColumnFormats(
(
- tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.CENTER_JUSTIFY),
+ tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.CENTER_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
@@ -257,16 +275,18 @@
os = StringIO()
table.printTable(os=os)
print os.getvalue()
-
+
+
+
def printMultiHistogramSummary(stats, index):
-
+
# Totals first
keys = ("requests", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s",)
totals = {
"T" : dict([(k, 0) for k in keys]),
"T-RESP-WR": dict([(k, 0) for k in keys]),
}
-
+
for stat in stats:
for i in ("T", "T-RESP-WR",):
totals[i][keys[0]] += stat[index][keys[0]]
@@ -276,11 +296,11 @@
print "5 minute average response histogram"
table = tables.Table()
table.addHeader(
- ("", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s"),
+ ("", "<10ms", "10ms<->100ms", "100ms<->1s", "1s<->10s", "10s<->30s", "30s<->60s", ">60s", "Over 1s", "Over 10s"),
)
table.setDefaultColumnFormats(
(
- tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.CENTER_JUSTIFY),
+ tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.CENTER_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
tables.Table.ColumnFormat("%d (%.1f%%)", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
@@ -308,8 +328,9 @@
os = StringIO()
table.printTable(os=os)
print os.getvalue()
-
-
+
+
+
def usage(error_msg=None):
if error_msg:
print error_msg
@@ -332,13 +353,14 @@
else:
sys.exit(0)
+
if __name__ == '__main__':
-
+
delay = 10
servers = ("data/Logs/state/caldavd-stats.sock",)
useTCP = False
- options, args = getopt.getopt(sys.argv[1:], "hs:t:", ["tcp=",])
+ options, args = getopt.getopt(sys.argv[1:], "hs:t:", ["tcp=", ])
for option, value in options:
if option == "-h":
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/lib-patches/cx_Oracle/nclob-fixes-and-prefetch.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/lib-patches/cx_Oracle/nclob-fixes-and-prefetch.patch 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/lib-patches/cx_Oracle/nclob-fixes-and-prefetch.patch 2012-10-01 12:44:46 UTC (rev 9884)
@@ -2,10 +2,11 @@
===================================================================
--- Connection.c 2011-03-19 16:05:30.000000000 -0700
+++ Connection.c 2012-08-01 09:22:17.000000000 -0700
-@@ -713,6 +713,19 @@
+@@ -713,6 +713,21 @@
if (newPasswordObj)
return Connection_ChangePassword(self, self->password, newPasswordObj);
++#ifdef OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE
+ /* set lob prefetch attribute to session */
+ ub4 default_lobprefetch_size = 4096; /* Set default size to 4K */
+ status = OCIAttrSet (self->sessionHandle, (ub4) OCI_HTYPE_SESSION,
@@ -19,6 +20,7 @@
+ return -1;
+ }
+
++#endif
// begin the session
Py_BEGIN_ALLOW_THREADS
status = OCISessionBegin(self->handle, self->environment->errorHandle,
Deleted: CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/trunk/locales/de/LC_MESSAGES/calendarserver.po 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,292 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2011-02-19 09:10+CET\n"
-"PO-Revision-Date: 2011-02-07 02:00+CET\n"
-"Last-Translator: Felix Möller <mail at felixmoeller.de>\n"
-"Language-Team: LANGUAGE <LL at li.org>\n"
-"Language: de\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-#: twistedcaldav/localization.py:180
-msgid "All day"
-msgstr "ganztägig"
-
-#: twistedcaldav/localization.py:208
-msgid "%(startTime)s to %(endTime)s"
-msgstr "%(startTime)s bis %(endTime)s"
-
-#: twistedcaldav/localization.py:223
-msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
-msgstr "%(dayName)s, %(dayNumber)d. %(monthName)s %(yearNumber)d"
-
-#: twistedcaldav/localization.py:239
-msgid "AM"
-msgstr "vormittags"
-
-#: twistedcaldav/localization.py:239
-msgid "PM"
-msgstr "nachmittags"
-
-#: twistedcaldav/localization.py:245
-msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
-msgstr "%(hour24Number)d:%(minuteNumber)02d"
-
-#: twistedcaldav/localization.py:267
-msgid "1 day"
-msgstr "1 Tag"
-
-#: twistedcaldav/localization.py:269
-msgid "%(dayCount)d days"
-msgstr "%(dayCount)d Tage"
-
-#: twistedcaldav/localization.py:277
-msgid "1 hour"
-msgstr "1 Stunde"
-
-#: twistedcaldav/localization.py:279
-msgid "%(hourCount)d hours"
-msgstr "%(hourCount)d Stunden"
-
-#: twistedcaldav/localization.py:283
-msgid "1 minute"
-msgstr "1 Minute"
-
-#: twistedcaldav/localization.py:285
-msgid "%(minuteCount)d minutes"
-msgstr "%(minuteCount)d Minuten"
-
-#: twistedcaldav/localization.py:289
-msgid "1 second"
-msgstr "1 Sekunde"
-
-#: twistedcaldav/localization.py:291
-msgid "%(secondCount)d seconds"
-msgstr "%(secondCount)d Sekunden"
-
-#: twistedcaldav/localization.py:303
-msgid "Monday"
-msgstr "Montag"
-
-#: twistedcaldav/localization.py:304
-msgid "Tuesday"
-msgstr "Dienstag"
-
-#: twistedcaldav/localization.py:305
-msgid "Wednesday"
-msgstr "Mittwoch"
-
-#: twistedcaldav/localization.py:306
-msgid "Thursday"
-msgstr "Donnerstag"
-
-#: twistedcaldav/localization.py:307
-msgid "Friday"
-msgstr "Freitag"
-
-#: twistedcaldav/localization.py:308
-msgid "Saturday"
-msgstr "Samstag"
-
-#: twistedcaldav/localization.py:309
-msgid "Sunday"
-msgstr "Sonntag"
-
-#: twistedcaldav/localization.py:313
-msgid "Mon"
-msgstr "Mo"
-
-#: twistedcaldav/localization.py:314
-msgid "Tue"
-msgstr "Di"
-
-#: twistedcaldav/localization.py:315
-msgid "Wed"
-msgstr "Mi"
-
-#: twistedcaldav/localization.py:316
-msgid "Thu"
-msgstr "Do"
-
-#: twistedcaldav/localization.py:317
-msgid "Fri"
-msgstr "Fr"
-
-#: twistedcaldav/localization.py:318
-msgid "Sun"
-msgstr "So"
-
-#: twistedcaldav/localization.py:319
-msgid "Sat"
-msgstr "Sa"
-
-#: twistedcaldav/localization.py:324
-msgid "January"
-msgstr "Januar"
-
-#: twistedcaldav/localization.py:325
-msgid "February"
-msgstr "Februar"
-
-#: twistedcaldav/localization.py:326
-msgid "March"
-msgstr "März"
-
-#: twistedcaldav/localization.py:327
-msgid "April"
-msgstr "April"
-
-#: twistedcaldav/localization.py:328
-msgid "May"
-msgstr "Mai"
-
-#: twistedcaldav/localization.py:329
-msgid "June"
-msgstr "Juni"
-
-#: twistedcaldav/localization.py:330
-msgid "July"
-msgstr "Juli"
-
-#: twistedcaldav/localization.py:331
-msgid "August"
-msgstr "August"
-
-#: twistedcaldav/localization.py:332
-msgid "September"
-msgstr "September"
-
-#: twistedcaldav/localization.py:333
-msgid "October"
-msgstr "Oktober"
-
-#: twistedcaldav/localization.py:334
-msgid "November"
-msgstr "November"
-
-#: twistedcaldav/localization.py:335
-msgid "December"
-msgstr "Dezember"
-
-#: twistedcaldav/localization.py:340
-msgid "JAN"
-msgstr "Jan"
-
-#: twistedcaldav/localization.py:341
-msgid "FEB"
-msgstr "Feb"
-
-#: twistedcaldav/localization.py:342
-msgid "MAR"
-msgstr "Mär"
-
-#: twistedcaldav/localization.py:343
-msgid "APR"
-msgstr "Apr"
-
-#: twistedcaldav/localization.py:344
-msgid "MAY"
-msgstr "Mai"
-
-#: twistedcaldav/localization.py:345
-msgid "JUN"
-msgstr "Jun"
-
-#: twistedcaldav/localization.py:346
-msgid "JUL"
-msgstr "Jul"
-
-#: twistedcaldav/localization.py:347
-msgid "AUG"
-msgstr "Aug"
-
-#: twistedcaldav/localization.py:348
-msgid "SEP"
-msgstr "Sep"
-
-#: twistedcaldav/localization.py:349
-msgid "OCT"
-msgstr "Okt"
-
-#: twistedcaldav/localization.py:350
-msgid "NOV"
-msgstr "Nov"
-
-#: twistedcaldav/localization.py:351
-msgid "DEC"
-msgstr "Dec"
-
-#: twistedcaldav/mail.py:1021
-msgid "Event canceled: %(summary)s"
-msgstr "Abgesagt: %(summary)s"
-
-#: twistedcaldav/mail.py:1023
-msgid "Event invitation: %(summary)s"
-msgstr "Einladung: %(summary)s"
-
-#: twistedcaldav/mail.py:1025
-msgid "Event update: %(summary)s"
-msgstr "Aktualisierung: %(summary)s"
-
-#: twistedcaldav/mail.py:1027
-msgid "Event reply: %(summary)s"
-msgstr "Antwort: %(summary)s"
-
-#: twistedcaldav/mail.py:1038
-msgid "Event Canceled"
-msgstr "Absage"
-
-#: twistedcaldav/mail.py:1041
-msgid "Event Invitation"
-msgstr "Einladung"
-
-#: twistedcaldav/mail.py:1043
-msgid "Event Update"
-msgstr "Aktualisierung"
-
-#: twistedcaldav/mail.py:1045
-msgid "Event Reply"
-msgstr "Antwort"
-
-#: twistedcaldav/mail.py:1047
-msgid "Date"
-msgstr "Datum"
-
-#: twistedcaldav/mail.py:1048
-msgid "Time"
-msgstr "Zeit"
-
-#: twistedcaldav/mail.py:1049
-msgid "Duration"
-msgstr "Dauer"
-
-#: twistedcaldav/mail.py:1050
-msgid "Occurs"
-msgstr "Wiederholung"
-
-#: twistedcaldav/mail.py:1051
-msgid "Description"
-msgstr "Beschreibung"
-
-#: twistedcaldav/mail.py:1052
-msgid "Organizer"
-msgstr "Veranstalter"
-
-#: twistedcaldav/mail.py:1053
-msgid "Attendees"
-msgstr "Teilnehmer"
-
-#: twistedcaldav/mail.py:1054
-msgid "Location"
-msgstr "Ort"
-
-#: twistedcaldav/mail.py:1247
-msgid "(Repeating)"
-msgstr "(wiederholend)"
Copied: CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po (from rev 9883, CalendarServer/trunk/locales/de/LC_MESSAGES/calendarserver.po)
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/de/LC_MESSAGES/calendarserver.po 2012-10-01 12:44:46 UTC (rev 9884)
@@ -0,0 +1,292 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2011-02-19 09:10+CET\n"
+"PO-Revision-Date: 2011-02-07 02:00+CET\n"
+"Last-Translator: Felix Möller <mail at felixmoeller.de>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+
+#: twistedcaldav/localization.py:180
+msgid "All day"
+msgstr "ganztägig"
+
+#: twistedcaldav/localization.py:208
+msgid "%(startTime)s to %(endTime)s"
+msgstr "%(startTime)s bis %(endTime)s"
+
+#: twistedcaldav/localization.py:223
+msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
+msgstr "%(dayName)s, %(dayNumber)d. %(monthName)s %(yearNumber)d"
+
+#: twistedcaldav/localization.py:239
+msgid "AM"
+msgstr "vormittags"
+
+#: twistedcaldav/localization.py:239
+msgid "PM"
+msgstr "nachmittags"
+
+#: twistedcaldav/localization.py:245
+msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
+msgstr "%(hour24Number)d:%(minuteNumber)02d"
+
+#: twistedcaldav/localization.py:267
+msgid "1 day"
+msgstr "1 Tag"
+
+#: twistedcaldav/localization.py:269
+msgid "%(dayCount)d days"
+msgstr "%(dayCount)d Tage"
+
+#: twistedcaldav/localization.py:277
+msgid "1 hour"
+msgstr "1 Stunde"
+
+#: twistedcaldav/localization.py:279
+msgid "%(hourCount)d hours"
+msgstr "%(hourCount)d Stunden"
+
+#: twistedcaldav/localization.py:283
+msgid "1 minute"
+msgstr "1 Minute"
+
+#: twistedcaldav/localization.py:285
+msgid "%(minuteCount)d minutes"
+msgstr "%(minuteCount)d Minuten"
+
+#: twistedcaldav/localization.py:289
+msgid "1 second"
+msgstr "1 Sekunde"
+
+#: twistedcaldav/localization.py:291
+msgid "%(secondCount)d seconds"
+msgstr "%(secondCount)d Sekunden"
+
+#: twistedcaldav/localization.py:303
+msgid "Monday"
+msgstr "Montag"
+
+#: twistedcaldav/localization.py:304
+msgid "Tuesday"
+msgstr "Dienstag"
+
+#: twistedcaldav/localization.py:305
+msgid "Wednesday"
+msgstr "Mittwoch"
+
+#: twistedcaldav/localization.py:306
+msgid "Thursday"
+msgstr "Donnerstag"
+
+#: twistedcaldav/localization.py:307
+msgid "Friday"
+msgstr "Freitag"
+
+#: twistedcaldav/localization.py:308
+msgid "Saturday"
+msgstr "Samstag"
+
+#: twistedcaldav/localization.py:309
+msgid "Sunday"
+msgstr "Sonntag"
+
+#: twistedcaldav/localization.py:313
+msgid "Mon"
+msgstr "Mo"
+
+#: twistedcaldav/localization.py:314
+msgid "Tue"
+msgstr "Di"
+
+#: twistedcaldav/localization.py:315
+msgid "Wed"
+msgstr "Mi"
+
+#: twistedcaldav/localization.py:316
+msgid "Thu"
+msgstr "Do"
+
+#: twistedcaldav/localization.py:317
+msgid "Fri"
+msgstr "Fr"
+
+#: twistedcaldav/localization.py:318
+msgid "Sun"
+msgstr "So"
+
+#: twistedcaldav/localization.py:319
+msgid "Sat"
+msgstr "Sa"
+
+#: twistedcaldav/localization.py:324
+msgid "January"
+msgstr "Januar"
+
+#: twistedcaldav/localization.py:325
+msgid "February"
+msgstr "Februar"
+
+#: twistedcaldav/localization.py:326
+msgid "March"
+msgstr "März"
+
+#: twistedcaldav/localization.py:327
+msgid "April"
+msgstr "April"
+
+#: twistedcaldav/localization.py:328
+msgid "May"
+msgstr "Mai"
+
+#: twistedcaldav/localization.py:329
+msgid "June"
+msgstr "Juni"
+
+#: twistedcaldav/localization.py:330
+msgid "July"
+msgstr "Juli"
+
+#: twistedcaldav/localization.py:331
+msgid "August"
+msgstr "August"
+
+#: twistedcaldav/localization.py:332
+msgid "September"
+msgstr "September"
+
+#: twistedcaldav/localization.py:333
+msgid "October"
+msgstr "Oktober"
+
+#: twistedcaldav/localization.py:334
+msgid "November"
+msgstr "November"
+
+#: twistedcaldav/localization.py:335
+msgid "December"
+msgstr "Dezember"
+
+#: twistedcaldav/localization.py:340
+msgid "JAN"
+msgstr "Jan"
+
+#: twistedcaldav/localization.py:341
+msgid "FEB"
+msgstr "Feb"
+
+#: twistedcaldav/localization.py:342
+msgid "MAR"
+msgstr "Mär"
+
+#: twistedcaldav/localization.py:343
+msgid "APR"
+msgstr "Apr"
+
+#: twistedcaldav/localization.py:344
+msgid "MAY"
+msgstr "Mai"
+
+#: twistedcaldav/localization.py:345
+msgid "JUN"
+msgstr "Jun"
+
+#: twistedcaldav/localization.py:346
+msgid "JUL"
+msgstr "Jul"
+
+#: twistedcaldav/localization.py:347
+msgid "AUG"
+msgstr "Aug"
+
+#: twistedcaldav/localization.py:348
+msgid "SEP"
+msgstr "Sep"
+
+#: twistedcaldav/localization.py:349
+msgid "OCT"
+msgstr "Okt"
+
+#: twistedcaldav/localization.py:350
+msgid "NOV"
+msgstr "Nov"
+
+#: twistedcaldav/localization.py:351
+msgid "DEC"
+msgstr "Dec"
+
+#: twistedcaldav/mail.py:1021
+msgid "Event canceled: %(summary)s"
+msgstr "Abgesagt: %(summary)s"
+
+#: twistedcaldav/mail.py:1023
+msgid "Event invitation: %(summary)s"
+msgstr "Einladung: %(summary)s"
+
+#: twistedcaldav/mail.py:1025
+msgid "Event update: %(summary)s"
+msgstr "Aktualisierung: %(summary)s"
+
+#: twistedcaldav/mail.py:1027
+msgid "Event reply: %(summary)s"
+msgstr "Antwort: %(summary)s"
+
+#: twistedcaldav/mail.py:1038
+msgid "Event Canceled"
+msgstr "Absage"
+
+#: twistedcaldav/mail.py:1041
+msgid "Event Invitation"
+msgstr "Einladung"
+
+#: twistedcaldav/mail.py:1043
+msgid "Event Update"
+msgstr "Aktualisierung"
+
+#: twistedcaldav/mail.py:1045
+msgid "Event Reply"
+msgstr "Antwort"
+
+#: twistedcaldav/mail.py:1047
+msgid "Date"
+msgstr "Datum"
+
+#: twistedcaldav/mail.py:1048
+msgid "Time"
+msgstr "Zeit"
+
+#: twistedcaldav/mail.py:1049
+msgid "Duration"
+msgstr "Dauer"
+
+#: twistedcaldav/mail.py:1050
+msgid "Occurs"
+msgstr "Wiederholung"
+
+#: twistedcaldav/mail.py:1051
+msgid "Description"
+msgstr "Beschreibung"
+
+#: twistedcaldav/mail.py:1052
+msgid "Organizer"
+msgstr "Veranstalter"
+
+#: twistedcaldav/mail.py:1053
+msgid "Attendees"
+msgstr "Teilnehmer"
+
+#: twistedcaldav/mail.py:1054
+msgid "Location"
+msgstr "Ort"
+
+#: twistedcaldav/mail.py:1247
+msgid "(Repeating)"
+msgstr "(wiederholend)"
Deleted: CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.po 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/locales/en_EN.ISO8859-1/LC_MESSAGES/calendarserver.po 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,266 +1,301 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+# Calendar Server Localization
+# Copyright (c) 2008-2012 Apple Inc. All rights reserved.
#
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2008-10-24 15:03+PDT\n"
+"POT-Creation-Date: 2012-09-28 10:13-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
-"Language-Team: LANGUAGE <LL at li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-#: localization.py:171
+#: twistedcaldav/localization.py:178
msgid "All day"
msgstr ""
-#: localization.py:177
+#: twistedcaldav/localization.py:205
+#, python-format
msgid "%(startTime)s to %(endTime)s"
msgstr ""
-#: localization.py:191
+#: twistedcaldav/localization.py:220
+#, python-format
msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "AM"
msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "PM"
msgstr ""
-#: localization.py:213
+#: twistedcaldav/localization.py:242
+#, python-format
msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
msgstr ""
-#: localization.py:236
+#: twistedcaldav/localization.py:266
+msgid "1 day"
+msgstr ""
+
+#: twistedcaldav/localization.py:268
+#, python-format
+msgid "%(dayCount)d days"
+msgstr ""
+
+#: twistedcaldav/localization.py:276
+msgid "1 hour"
+msgstr ""
+
+#: twistedcaldav/localization.py:278
+#, python-format
+msgid "%(hourCount)d hours"
+msgstr ""
+
+#: twistedcaldav/localization.py:282
+msgid "1 minute"
+msgstr ""
+
+#: twistedcaldav/localization.py:284
+#, python-format
+msgid "%(minuteCount)d minutes"
+msgstr ""
+
+#: twistedcaldav/localization.py:288
+msgid "1 second"
+msgstr ""
+
+#: twistedcaldav/localization.py:290
+#, python-format
+msgid "%(secondCount)d seconds"
+msgstr ""
+
+#: twistedcaldav/localization.py:302
msgid "Monday"
msgstr ""
-#: localization.py:237
+#: twistedcaldav/localization.py:303
msgid "Tuesday"
msgstr ""
-#: localization.py:238
+#: twistedcaldav/localization.py:304
msgid "Wednesday"
msgstr ""
-#: localization.py:239
+#: twistedcaldav/localization.py:305
msgid "Thursday"
msgstr ""
-#: localization.py:240
+#: twistedcaldav/localization.py:306
msgid "Friday"
msgstr ""
-#: localization.py:241
+#: twistedcaldav/localization.py:307
msgid "Saturday"
msgstr ""
-#: localization.py:242
+#: twistedcaldav/localization.py:308
msgid "Sunday"
msgstr ""
-#: localization.py:246
+#: twistedcaldav/localization.py:312
msgid "Mon"
msgstr ""
-#: localization.py:247
+#: twistedcaldav/localization.py:313
msgid "Tue"
msgstr ""
-#: localization.py:248
+#: twistedcaldav/localization.py:314
msgid "Wed"
msgstr ""
-#: localization.py:249
+#: twistedcaldav/localization.py:315
msgid "Thu"
msgstr ""
-#: localization.py:250
+#: twistedcaldav/localization.py:316
msgid "Fri"
msgstr ""
-#: localization.py:251
+#: twistedcaldav/localization.py:317
msgid "Sun"
msgstr ""
-#: localization.py:252
+#: twistedcaldav/localization.py:318
msgid "Sat"
msgstr ""
-#: localization.py:257
+#: twistedcaldav/localization.py:323
msgid "January"
msgstr ""
-#: localization.py:258
+#: twistedcaldav/localization.py:324
msgid "February"
msgstr ""
-#: localization.py:259
+#: twistedcaldav/localization.py:325
msgid "March"
msgstr ""
-#: localization.py:260
+#: twistedcaldav/localization.py:326
msgid "April"
msgstr ""
-#: localization.py:261 localization.py:277
+#: twistedcaldav/localization.py:327
msgid "May"
msgstr ""
-#: localization.py:262
+#: twistedcaldav/localization.py:328
msgid "June"
msgstr ""
-#: localization.py:263
+#: twistedcaldav/localization.py:329
msgid "July"
msgstr ""
-#: localization.py:264
+#: twistedcaldav/localization.py:330
msgid "August"
msgstr ""
-#: localization.py:265
+#: twistedcaldav/localization.py:331
msgid "September"
msgstr ""
-#: localization.py:266
+#: twistedcaldav/localization.py:332
msgid "October"
msgstr ""
-#: localization.py:267
+#: twistedcaldav/localization.py:333
msgid "November"
msgstr ""
-#: localization.py:268
+#: twistedcaldav/localization.py:334
msgid "December"
msgstr ""
-#: localization.py:273
-msgid "Jan"
+#: twistedcaldav/localization.py:339
+msgid "JAN"
msgstr ""
-#: localization.py:274
-msgid "Feb"
+#: twistedcaldav/localization.py:340
+msgid "FEB"
msgstr ""
-#: localization.py:275
-msgid "Mar"
+#: twistedcaldav/localization.py:341
+msgid "MAR"
msgstr ""
-#: localization.py:276
-msgid "Apr"
+#: twistedcaldav/localization.py:342
+msgid "APR"
msgstr ""
-#: localization.py:278
-msgid "Jun"
+#: twistedcaldav/localization.py:343
+msgid "MAY"
msgstr ""
-#: localization.py:279
-msgid "Jul"
+#: twistedcaldav/localization.py:344
+msgid "JUN"
msgstr ""
-#: localization.py:280
-msgid "Aug"
+#: twistedcaldav/localization.py:345
+msgid "JUL"
msgstr ""
-#: localization.py:281
-msgid "Sep"
+#: twistedcaldav/localization.py:346
+msgid "AUG"
msgstr ""
-#: localization.py:282
-msgid "Oct"
+#: twistedcaldav/localization.py:347
+msgid "SEP"
msgstr ""
-#: localization.py:283
-msgid "Nov"
+#: twistedcaldav/localization.py:348
+msgid "OCT"
msgstr ""
-#: localization.py:284
-msgid "Dec"
+#: twistedcaldav/localization.py:349
+msgid "NOV"
msgstr ""
-#: mail.py:726 mail.py:755 mail.py:792
-msgid "Event cancelled"
+#: twistedcaldav/localization.py:350
+msgid "DEC"
msgstr ""
-#: mail.py:727
+#: twistedcaldav/mail.py:267
+#, python-format
+msgid "Event canceled: %(summary)s"
+msgstr ""
+
+#: twistedcaldav/mail.py:269
+#, python-format
msgid "Event invitation: %(summary)s"
msgstr ""
+#: twistedcaldav/mail.py:271
+#, python-format
msgid "Event update: %(summary)s"
msgstr ""
-#: mail.py:736
+#: twistedcaldav/mail.py:273
+#, python-format
+msgid "Event reply: %(summary)s"
+msgstr ""
+
+#: twistedcaldav/mail.py:276
+msgid "Event Canceled"
+msgstr ""
+
+#: twistedcaldav/mail.py:279
msgid "Event Invitation"
msgstr ""
+#: twistedcaldav/mail.py:281
msgid "Event Update"
msgstr ""
-#: mail.py:737
+#: twistedcaldav/mail.py:283
+msgid "Event Reply"
+msgstr ""
+
+#: twistedcaldav/mail.py:286
msgid "Date"
msgstr ""
-#: mail.py:738
+#: twistedcaldav/mail.py:287
msgid "Time"
msgstr ""
-#: mail.py:739
-msgid "Description"
+#: twistedcaldav/mail.py:288
+msgid "Duration"
msgstr ""
-#: mail.py:740
-msgid "Organizer"
+#: twistedcaldav/mail.py:289
+msgid "Occurs"
msgstr ""
-#: mail.py:741
-msgid "Attendees"
+#: twistedcaldav/mail.py:290
+msgid "Description"
msgstr ""
-#: mail.py:742
-msgid "Location"
+#: twistedcaldav/mail.py:291
+msgid "URL"
msgstr ""
-msgid "Duration"
+#: twistedcaldav/mail.py:292
+msgid "Organizer"
msgstr ""
-msgid "Occurs"
+#: twistedcaldav/mail.py:293
+msgid "Attendees"
msgstr ""
-msgid "Repeating"
+#: twistedcaldav/mail.py:294
+msgid "Location"
msgstr ""
-msgid "Once"
+#: twistedcaldav/mail.py:1656
+msgid "(Repeating)"
msgstr ""
-
-msgid "1 day"
-msgstr
-
-msgid "%(dayCount)d days"
-msgstr
-
-msgid "1 hour"
-
-msgid "%(hourCount)d hours"
-msgstr
-
-msgid "1 minute"
-msgstr
-
-msgid "%(minuteCount)d minutes"
-msgstr
-
-msgid "1 second"
-msgstr
-
-msgid "%(secondCount)d seconds"
-msgstr
-
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/support/Makefile.Apple 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/support/Makefile.Apple 2012-10-01 12:44:46 UTC (rev 9884)
@@ -27,11 +27,14 @@
# Include common makefile targets for B&I
include $(MAKEFILEPATH)/CoreOS/ReleaseControl/Common.make
--include /AppleInternal/ServerTools/ServerBuildVariables.xcconfig
+include /AppleInternal/ServerTools/ServerBuildVariables.xcconfig
SIPP = $(SERVER_INSTALL_PATH_PREFIX)
SERVERSETUP = $(SIPP)$(NSSYSTEMDIR)$(NSLIBRARYSUBDIR)/ServerSetup
+Cruft += .dependencies
+Extra_Environment += PATH="$(SIPP)/usr/bin:$$PATH"
+
CALDAVDSUBDIR = /caldavd
WEBAPPSSUBDIR = /apache2/webapps
Deleted: CalendarServer/branches/users/cdaboo/ischedule-dkim/support/pygettext.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/support/pygettext.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/support/pygettext.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,669 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-# Originally written by Barry Warsaw <barry at zope.com>
-#
-# Minimally patched to make it even more xgettext compatible
-# by Peter Funk <pf at artcom-gmbh.de>
-#
-# 2002-11-22 J\xFCrgen Hermann <jh at web.de>
-# Added checks that _() only contains string literals, and
-# command line args are resolved to module lists, i.e. you
-# can now pass a filename, a module or package name, or a
-# directory (including globbing chars, important for Win32).
-# Made docstring fit in 80 chars wide displays using pydoc.
-#
-
-# for selftesting
-try:
- import fintl
- _ = fintl.gettext
-except ImportError:
- _ = lambda s: s
-
-__doc__ = _("""pygettext -- Python equivalent of xgettext(1)
-
-Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the
-internationalization of C programs. Most of these tools are independent of
-the programming language and can be used from within Python programs.
-Martin von Loewis' work[1] helps considerably in this regard.
-
-There's one problem though; xgettext is the program that scans source code
-looking for message strings, but it groks only C (or C++). Python
-introduces a few wrinkles, such as dual quoting characters, triple quoted
-strings, and raw strings. xgettext understands none of this.
-
-Enter pygettext, which uses Python's standard tokenize module to scan
-Python source code, generating .pot files identical to what GNU xgettext[2]
-generates for C and C++ code. From there, the standard GNU tools can be
-used.
-
-A word about marking Python strings as candidates for translation. GNU
-xgettext recognizes the following keywords: gettext, dgettext, dcgettext,
-and gettext_noop. But those can be a lot of text to include all over your
-code. C and C++ have a trick: they use the C preprocessor. Most
-internationalized C source includes a #define for gettext() to _() so that
-what has to be written in the source is much less. Thus these are both
-translatable strings:
-
- gettext("Translatable String")
- _("Translatable String")
-
-Python of course has no preprocessor so this doesn't work so well. Thus,
-pygettext searches only for _() by default, but see the -k/--keyword flag
-below for how to augment this.
-
- [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html
- [2] http://www.gnu.org/software/gettext/gettext.html
-
-NOTE: pygettext attempts to be option and feature compatible with GNU
-xgettext where ever possible. However some options are still missing or are
-not fully implemented. Also, xgettext's use of command line switches with
-option arguments is broken, and in these cases, pygettext just defines
-additional switches.
-
-Usage: pygettext [options] inputfile ...
-
-Options:
-
- -a
- --extract-all
- Extract all strings.
-
- -d name
- --default-domain=name
- Rename the default output file from messages.pot to name.pot.
-
- -E
- --escape
- Replace non-ASCII characters with octal escape sequences.
-
- -D
- --docstrings
- Extract module, class, method, and function docstrings. These do
- not need to be wrapped in _() markers, and in fact cannot be for
- Python to consider them docstrings. (See also the -X option).
-
- -h
- --help
- Print this help message and exit.
-
- -k word
- --keyword=word
- Keywords to look for in addition to the default set, which are:
- %(DEFAULTKEYWORDS)s
-
- You can have multiple -k flags on the command line.
-
- -K
- --no-default-keywords
- Disable the default set of keywords (see above). Any keywords
- explicitly added with the -k/--keyword option are still recognized.
-
- --no-location
- Do not write filename/lineno location comments.
-
- -n
- --add-location
- Write filename/lineno location comments indicating where each
- extracted string is found in the source. These lines appear before
- each msgid. The style of comments is controlled by the -S/--style
- option. This is the default.
-
- -o filename
- --output=filename
- Rename the default output file from messages.pot to filename. If
- filename is `-' then the output is sent to standard out.
-
- -p dir
- --output-dir=dir
- Output files will be placed in directory dir.
-
- -S stylename
- --style stylename
- Specify which style to use for location comments. Two styles are
- supported:
-
- Solaris # File: filename, line: line-number
- GNU #: filename:line
-
- The style name is case insensitive. GNU style is the default.
-
- -v
- --verbose
- Print the names of the files being processed.
-
- -V
- --version
- Print the version of pygettext and exit.
-
- -w columns
- --width=columns
- Set width of output to columns.
-
- -x filename
- --exclude-file=filename
- Specify a file that contains a list of strings that are not be
- extracted from the input files. Each string to be excluded must
- appear on a line by itself in the file.
-
- -X filename
- --no-docstrings=filename
- Specify a file that contains a list of files (one per line) that
- should not have their docstrings extracted. This is only useful in
- conjunction with the -D option above.
-
-If `inputfile' is -, standard input is read.
-""")
-
-import os
-import imp
-import sys
-import glob
-import time
-import getopt
-import token
-import tokenize
-import operator
-
-__version__ = '1.5'
-
-default_keywords = ['_']
-DEFAULTKEYWORDS = ', '.join(default_keywords)
-
-EMPTYSTRING = ''
-
-
-
-# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
-# there.
-pot_header = _('''\
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\\n"
-"POT-Creation-Date: %(time)s\\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
-"Last-Translator: FULL NAME <EMAIL at ADDRESS>\\n"
-"Language-Team: LANGUAGE <LL at li.org>\\n"
-"MIME-Version: 1.0\\n"
-"Content-Type: text/plain; charset=CHARSET\\n"
-"Content-Transfer-Encoding: ENCODING\\n"
-"Generated-By: pygettext.py %(version)s\\n"
-
-''')
-
-
-def usage(code, msg=''):
- print >> sys.stderr, __doc__ % globals()
- if msg:
- print >> sys.stderr, msg
- sys.exit(code)
-
-
-
-escapes = []
-
-def make_escapes(pass_iso8859):
- global escapes
- if pass_iso8859:
- # Allow iso-8859 characters to pass through so that e.g. 'msgid
- # "H\xF6he"' would result not result in 'msgid "H\366he"'. Otherwise we
- # escape any character outside the 32..126 range.
- mod = 128
- else:
- mod = 256
- for i in range(256):
- if 32 <= (i % mod) <= 126:
- escapes.append(chr(i))
- else:
- escapes.append("\\%03o" % i)
- escapes[ord('\\')] = '\\\\'
- escapes[ord('\t')] = '\\t'
- escapes[ord('\r')] = '\\r'
- escapes[ord('\n')] = '\\n'
- escapes[ord('\"')] = '\\"'
-
-
-def escape(s):
- global escapes
- s = list(s)
- for i in range(len(s)):
- s[i] = escapes[ord(s[i])]
- return EMPTYSTRING.join(s)
-
-
-def safe_eval(s):
- # unwrap quotes, safely
- return eval(s, {'__builtins__':{}}, {})
-
-
-def normalize(s):
- # This converts the various Python string types into a format that is
- # appropriate for .po files, namely much closer to C style.
- lines = s.split('\n')
- if len(lines) == 1:
- s = '"' + escape(s) + '"'
- else:
- if not lines[-1]:
- del lines[-1]
- lines[-1] = lines[-1] + '\n'
- for i in range(len(lines)):
- lines[i] = escape(lines[i])
- lineterm = '\\n"\n"'
- s = '""\n"' + lineterm.join(lines) + '"'
- return s
-
-
-def containsAny(str, set):
- """Check whether 'str' contains ANY of the chars in 'set'"""
- return 1 in [c in str for c in set]
-
-
-def _visit_pyfiles(list, dirname, names):
- """Helper for getFilesForName()."""
- # get extension for python source files
- if not globals().has_key('_py_ext'):
- global _py_ext
- _py_ext = [triple[0] for triple in imp.get_suffixes()
- if triple[2] == imp.PY_SOURCE][0]
-
- # don't recurse into CVS directories
- if 'CVS' in names:
- names.remove('CVS')
-
- # add all *.py files to list
- list.extend(
- [os.path.join(dirname, file) for file in names
- if os.path.splitext(file)[1] == _py_ext]
- )
-
-
-def _get_modpkg_path(dotted_name, pathlist=None):
- """Get the filesystem path for a module or a package.
-
- Return the file system path to a file for a module, and to a directory for
- a package. Return None if the name is not found, or is a builtin or
- extension module.
- """
- # split off top-most name
- parts = dotted_name.split('.', 1)
-
- if len(parts) > 1:
- # we have a dotted path, import top-level package
- try:
- file, pathname, description = imp.find_module(parts[0], pathlist)
- if file: file.close()
- except ImportError:
- return None
-
- # check if it's indeed a package
- if description[2] == imp.PKG_DIRECTORY:
- # recursively handle the remaining name parts
- pathname = _get_modpkg_path(parts[1], [pathname])
- else:
- pathname = None
- else:
- # plain name
- try:
- file, pathname, description = imp.find_module(
- dotted_name, pathlist)
- if file:
- file.close()
- if description[2] not in [imp.PY_SOURCE, imp.PKG_DIRECTORY]:
- pathname = None
- except ImportError:
- pathname = None
-
- return pathname
-
-
-def getFilesForName(name):
- """Get a list of module files for a filename, a module or package name,
- or a directory.
- """
- if not os.path.exists(name):
- # check for glob chars
- if containsAny(name, "*?[]"):
- files = glob.glob(name)
- list = []
- for file in files:
- list.extend(getFilesForName(file))
- return list
-
- # try to find module or package
- name = _get_modpkg_path(name)
- if not name:
- return []
-
- if os.path.isdir(name):
- # find all python files in directory
- list = []
- os.path.walk(name, _visit_pyfiles, list)
- return list
- elif os.path.exists(name):
- # a single file
- return [name]
-
- return []
-
-
-class TokenEater:
- def __init__(self, options):
- self.__options = options
- self.__messages = {}
- self.__state = self.__waiting
- self.__data = []
- self.__lineno = -1
- self.__freshmodule = 1
- self.__curfile = None
-
- def __call__(self, ttype, tstring, stup, etup, line):
- # dispatch
-## import token
-## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \
-## 'tstring:', tstring
- self.__state(ttype, tstring, stup[0])
-
- def __waiting(self, ttype, tstring, lineno):
- opts = self.__options
- # Do docstring extractions, if enabled
- if opts.docstrings and not opts.nodocstrings.get(self.__curfile):
- # module docstring?
- if self.__freshmodule:
- if ttype == tokenize.STRING:
- self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
- self.__freshmodule = 0
- elif ttype not in (tokenize.COMMENT, tokenize.NL):
- self.__freshmodule = 0
- return
- # class docstring?
- if ttype == tokenize.NAME and tstring in ('class', 'def'):
- self.__state = self.__suiteseen
- return
- if ttype == tokenize.NAME and tstring in opts.keywords:
- self.__state = self.__keywordseen
-
- def __suiteseen(self, ttype, tstring, lineno):
- # ignore anything until we see the colon
- if ttype == tokenize.OP and tstring == ':':
- self.__state = self.__suitedocstring
-
- def __suitedocstring(self, ttype, tstring, lineno):
- # ignore any intervening noise
- if ttype == tokenize.STRING:
- self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
- self.__state = self.__waiting
- elif ttype not in (tokenize.NEWLINE, tokenize.INDENT,
- tokenize.COMMENT):
- # there was no class docstring
- self.__state = self.__waiting
-
- def __keywordseen(self, ttype, tstring, lineno):
- if ttype == tokenize.OP and tstring == '(':
- self.__data = []
- self.__lineno = lineno
- self.__state = self.__openseen
- else:
- self.__state = self.__waiting
-
- def __openseen(self, ttype, tstring, lineno):
- if ttype == tokenize.OP and tstring == ')':
- # We've seen the last of the translatable strings. Record the
- # line number of the first line of the strings and update the list
- # of messages seen. Reset state for the next batch. If there
- # were no strings inside _(), then just ignore this entry.
- if self.__data:
- self.__addentry(EMPTYSTRING.join(self.__data))
- self.__state = self.__waiting
- elif ttype == tokenize.STRING:
- self.__data.append(safe_eval(tstring))
- elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT,
- token.NEWLINE, tokenize.NL]:
- # warn if we see anything else than STRING or whitespace
- print >> sys.stderr, _(
- '*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"'
- ) % {
- 'token': tstring,
- 'file': self.__curfile,
- 'lineno': self.__lineno
- }
- self.__state = self.__waiting
-
- def __addentry(self, msg, lineno=None, isdocstring=0):
- if lineno is None:
- lineno = self.__lineno
- if not msg in self.__options.toexclude:
- entry = (self.__curfile, lineno)
- self.__messages.setdefault(msg, {})[entry] = isdocstring
-
- def set_filename(self, filename):
- self.__curfile = filename
- self.__freshmodule = 1
-
- def write(self, fp):
- options = self.__options
- timestamp = time.strftime('%Y-%m-%d %H:%M+%Z')
- # The time stamp in the header doesn't have the same format as that
- # generated by xgettext...
- print >> fp, pot_header % {'time': timestamp, 'version': __version__}
- # Sort the entries. First sort each particular entry's keys, then
- # sort all the entries by their first item.
- reverse = {}
- for k, v in self.__messages.items():
- keys = v.keys()
- keys.sort()
- reverse.setdefault(tuple(keys), []).append((k, v))
- rkeys = reverse.keys()
- rkeys.sort()
- for rkey in rkeys:
- rentries = reverse[rkey]
- rentries.sort()
- for k, v in rentries:
- isdocstring = 0
- # If the entry was gleaned out of a docstring, then add a
- # comment stating so. This is to aid translators who may wish
- # to skip translating some unimportant docstrings.
- if reduce(operator.__add__, v.values()):
- isdocstring = 1
- # k is the message string, v is a dictionary-set of (filename,
- # lineno) tuples. We want to sort the entries in v first by
- # file name and then by line number.
- v = v.keys()
- v.sort()
- if not options.writelocations:
- pass
- # location comments are different b/w Solaris and GNU:
- elif options.locationstyle == options.SOLARIS:
- for filename, lineno in v:
- d = {'filename': filename, 'lineno': lineno}
- print >>fp, _(
- '# File: %(filename)s, line: %(lineno)d') % d
- elif options.locationstyle == options.GNU:
- # fit as many locations on one line, as long as the
- # resulting line length doesn't exceeds 'options.width'
- locline = '#:'
- for filename, lineno in v:
- d = {'filename': filename, 'lineno': lineno}
- s = _(' %(filename)s:%(lineno)d') % d
- if len(locline) + len(s) <= options.width:
- locline = locline + s
- else:
- print >> fp, locline
- locline = "#:" + s
- if len(locline) > 2:
- print >> fp, locline
- if isdocstring:
- print >> fp, '#, docstring'
- print >> fp, 'msgid', normalize(k)
- print >> fp, 'msgstr ""\n'
-
-
-
-def main():
- global default_keywords
- try:
- opts, args = getopt.getopt(
- sys.argv[1:],
- 'ad:DEhk:Kno:p:S:Vvw:x:X:',
- ['extract-all', 'default-domain=', 'escape', 'help',
- 'keyword=', 'no-default-keywords',
- 'add-location', 'no-location', 'output=', 'output-dir=',
- 'style=', 'verbose', 'version', 'width=', 'exclude-file=',
- 'docstrings', 'no-docstrings',
- ])
- except getopt.error, msg:
- usage(1, msg)
-
- # for holding option values
- class Options:
- # constants
- GNU = 1
- SOLARIS = 2
- # defaults
- extractall = 0 # FIXME: currently this option has no effect at all.
- escape = 0
- keywords = []
- outpath = ''
- outfile = 'messages.pot'
- writelocations = 1
- locationstyle = GNU
- verbose = 0
- width = 78
- excludefilename = ''
- docstrings = 0
- nodocstrings = {}
-
- options = Options()
- locations = {'gnu' : options.GNU,
- 'solaris' : options.SOLARIS,
- }
-
- # parse options
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-a', '--extract-all'):
- options.extractall = 1
- elif opt in ('-d', '--default-domain'):
- options.outfile = arg + '.pot'
- elif opt in ('-E', '--escape'):
- options.escape = 1
- elif opt in ('-D', '--docstrings'):
- options.docstrings = 1
- elif opt in ('-k', '--keyword'):
- options.keywords.append(arg)
- elif opt in ('-K', '--no-default-keywords'):
- default_keywords = []
- elif opt in ('-n', '--add-location'):
- options.writelocations = 1
- elif opt in ('--no-location',):
- options.writelocations = 0
- elif opt in ('-S', '--style'):
- options.locationstyle = locations.get(arg.lower())
- if options.locationstyle is None:
- usage(1, _('Invalid value for --style: %s') % arg)
- elif opt in ('-o', '--output'):
- options.outfile = arg
- elif opt in ('-p', '--output-dir'):
- options.outpath = arg
- elif opt in ('-v', '--verbose'):
- options.verbose = 1
- elif opt in ('-V', '--version'):
- print _('pygettext.py (xgettext for Python) %s') % __version__
- sys.exit(0)
- elif opt in ('-w', '--width'):
- try:
- options.width = int(arg)
- except ValueError:
- usage(1, _('--width argument must be an integer: %s') % arg)
- elif opt in ('-x', '--exclude-file'):
- options.excludefilename = arg
- elif opt in ('-X', '--no-docstrings'):
- fp = open(arg)
- try:
- while 1:
- line = fp.readline()
- if not line:
- break
- options.nodocstrings[line[:-1]] = 1
- finally:
- fp.close()
-
- # calculate escapes
- make_escapes(options.escape)
-
- # calculate all keywords
- options.keywords.extend(default_keywords)
-
- # initialize list of strings to exclude
- if options.excludefilename:
- try:
- fp = open(options.excludefilename)
- options.toexclude = fp.readlines()
- fp.close()
- except IOError:
- print >> sys.stderr, _(
- "Can't read --exclude-file: %s") % options.excludefilename
- sys.exit(1)
- else:
- options.toexclude = []
-
- # resolve args to module lists
- expanded = []
- for arg in args:
- if arg == '-':
- expanded.append(arg)
- else:
- expanded.extend(getFilesForName(arg))
- args = expanded
-
- # slurp through all the files
- eater = TokenEater(options)
- for filename in args:
- if filename == '-':
- if options.verbose:
- print _('Reading standard input')
- fp = sys.stdin
- closep = 0
- else:
- if options.verbose:
- print _('Working on %s') % filename
- fp = open(filename)
- closep = 1
- try:
- eater.set_filename(filename)
- try:
- tokenize.tokenize(fp.readline, eater)
- except tokenize.TokenError, e:
- print >> sys.stderr, '%s: %s, line %d, column %d' % (
- e[0], filename, e[1][0], e[1][1])
- finally:
- if closep:
- fp.close()
-
- # write the output
- if options.outfile == '-':
- fp = sys.stdout
- closep = 0
- else:
- if options.outpath:
- options.outfile = os.path.join(options.outpath, options.outfile)
- fp = open(options.outfile, 'w')
- closep = 1
- try:
- eater.write(fp)
- finally:
- if closep:
- fp.close()
-
-
-if __name__ == '__main__':
- main()
- # some more test strings
- _(u'a unicode string')
- # this one creates a warning
- _('*** Seen unexpected token "%(token)s"') % {'token': 'test'}
- _('more' 'than' 'one' 'string')
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/support/submit
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/support/submit 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/support/submit 2012-10-01 12:44:46 UTC (rev 9884)
@@ -38,6 +38,7 @@
submission_enabled=true;
ignore_uncommitted_changes=false;
+ build_no_verify_source=false;
usage ()
{
@@ -46,19 +47,20 @@
if [ "${1-}" != "-" ]; then echo "$@"; echo; fi;
echo "Usage: ${program} release [release ...]";
- echo " ${program} -b[ip]";
+ echo " ${program} release -b[ipn]";
echo "";
echo "Options:";
echo " -b Run buildit";
echo " -i Install resulting build on this system";
echo " -p Create a package with the resulting build";
echo " -f Ignore uncommitted changes";
+ echo " -n skip buildit source verification";
if [ "${1-}" == "-" ]; then return 0; fi;
exit 64;
}
-while getopts 'hbipf' option; do
+while getopts 'hbipfn' option; do
case "$option" in
'?') usage; ;;
'h') usage -; exit 0; ;;
@@ -66,6 +68,7 @@
'i') install=true; ;;
'p') package=true; ;;
'f') ignore_uncommitted_changes=true; ;;
+ 'n') build_no_verify_source=true; ;;
esac;
done;
shift $((${OPTIND} - 1));
@@ -73,6 +76,7 @@
if ! "${build}"; then
if "${install}"; then usage "-i flag requires -b"; fi;
if "${package}"; then usage "-p flag requires -b"; fi;
+ if "${build_no_verify_source}"; then usage "-n flag requires -b"; fi;
if ! "${submission_enabled}"; then
echo "Submissions from this branch are not enabled.";
@@ -178,10 +182,16 @@
for release in "${releases}"; do
release_flags="${release_flags} -release ${release}";
done;
+
+ if "${build_no_verify_source}"; then
+ verify_flags=" -noverifysource";
+ else
+ verify_flags="";
+ fi;
sudo buildit "${wc}" \
$(file /System/Library/Frameworks/Python.framework/Versions/Current/Python | sed -n -e 's|^.*(for architecture \([^)][^)]*\).*$|-arch \1|p' | sed 's|ppc7400|ppc|') \
- ${merge_flags}${release_flags};
+ ${merge_flags}${release_flags}${verify_flags};
if "${package}"; then
package_file="${project_version}.tgz";
@@ -199,4 +209,4 @@
submitproject "${wc}" ${releases};
fi;
-rm -rf "${tmp}";
+sudo rm -rf "${tmp}";
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/support/version.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/support/version.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/support/version.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -24,7 +24,7 @@
# Compute the version number.
#
- base_version = "4.2"
+ base_version = "5.0"
branches = (
"tags/release/CalendarServer-" + base_version,
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/syntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/syntax.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/syntax.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -313,10 +313,18 @@
class ExpressionSyntax(Syntax):
__eq__ = comparison('=')
__ne__ = comparison('!=')
+
+ # NB: these operators "cannot be used with lists" (see ORA-01796)
__gt__ = comparison('>')
__ge__ = comparison('>=')
__lt__ = comparison('<')
__le__ = comparison('<=')
+
+ # TODO: operators aren't really comparisons; these should behave slightly
+ # differently. (For example; in Oracle, 'select 3 = 4 from dual' doesn't
+ # work, but 'select 3 + 4 from dual' does; similarly, you can't do 'select *
+ # from foo where 3 + 4', but you can do 'select * from foo where 3 + 4 >
+ # 0'.)
__add__ = comparison("+")
__sub__ = comparison("-")
__div__= comparison("/")
@@ -773,7 +781,7 @@
class ResultAliasSyntax(ExpressionSyntax):
-
+
def __init__(self, expression, alias=None):
self.expression = expression
self.alias = alias
@@ -797,7 +805,7 @@
class AliasReferenceSyntax(ExpressionSyntax):
-
+
def __init__(self, resultAlias):
self.resultAlias = resultAlias
@@ -907,6 +915,12 @@
if (isinstance(self.b, CompoundComparison)
and self.b.op == 'or' and self.op == 'and'):
result = _inParens(result)
+ if isinstance(self.b, Tuple):
+ # If the right-hand side of the comparison is a Tuple, it needs to
+ # be double-parenthesized in Oracle, as per
+ # http://docs.oracle.com/cd/B28359_01/server.111/b28286/expressions015.htm#i1033664
+ # because it is an expression list.
+ result = _inParens(result)
stmt.append(result)
return stmt
@@ -997,6 +1011,10 @@
self.columns = columns
+ def __iter__(self):
+ return iter(self.columns)
+
+
def subSQL(self, queryGenerator, allTables):
return _inParens(_commaJoined(c.subSQL(queryGenerator, allTables)
for c in self.columns))
@@ -1010,24 +1028,24 @@
"""
A UNION, INTERSECT, or EXCEPT construct used inside a SELECT.
"""
-
+
OPTYPE_ALL = "all"
OPTYPE_DISTINCT = "distinct"
def __init__(self, selects, optype=None):
"""
-
+
@param selects: a single Select or a list of Selects
@type selects: C{list} or L{Select}
@param optype: whether to use the ALL, DISTINCT constructs: C{None} use neither, OPTYPE_ALL, or OPTYPE_DISTINCT
@type optype: C{str}
"""
-
+
if isinstance(selects, Select):
selects = (selects,)
self.selects = selects
self.optype = optype
-
+
for select in self.selects:
if not isinstance(select, Select):
raise DALError("Must have SELECT statements in a set expression")
@@ -1086,7 +1104,7 @@
self.From = From
self.Where = Where
self.Distinct = Distinct
- if not isinstance(OrderBy, (list, tuple, type(None))):
+ if not isinstance(OrderBy, (Tuple, list, tuple, type(None))):
OrderBy = [OrderBy]
self.OrderBy = OrderBy
if not isinstance(GroupBy, (list, tuple, type(None))):
@@ -1102,7 +1120,7 @@
_checkColumnsMatchTables(columns, From.tables())
columns = _SomeColumns(columns)
self.columns = columns
-
+
self.ForUpdate = ForUpdate
self.NoWait = NoWait
self.Ascending = Ascending
@@ -1235,8 +1253,8 @@
for table in self.From.tables():
tables.add(table.model)
return [TableSyntax(table) for table in tables]
-
+
def _commaJoined(stmts):
first = True
cstatement = SQLFragment()
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/test/test_sqlsyntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/test/test_sqlsyntax.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/enterprise/dal/test/test_sqlsyntax.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -37,6 +37,8 @@
from twext.enterprise.test.test_adbapi2 import resultOf, AssertResultHelper
from twisted.internet.defer import succeed
from twisted.trial.unittest import TestCase
+from twext.enterprise.dal.syntax import Tuple
+from twext.enterprise.dal.syntax import Constant
@@ -288,6 +290,21 @@
)
+ def test_orderByParens(self):
+ """
+ L{Select}'s L{OrderBy} paraneter, if specified as a L{Tuple}, generates
+ an SQL expression I{without} parentheses, since the standard format
+ does not allow an arbitrary sort expression but rather a list of
+ columns.
+ """
+ self.assertEquals(
+ Select(From=self.schema.FOO,
+ OrderBy=Tuple([self.schema.FOO.BAR,
+ self.schema.FOO.BAZ])).toSQL(),
+ SQLFragment("select * from FOO order by BAR, BAZ")
+ )
+
+
def test_forUpdate(self):
"""
L{Select}'s L{ForUpdate} parameter generates a 'for update' clause at
@@ -805,13 +822,13 @@
)
# Check various error situations
-
+
# No count not allowed
self.assertRaises(DALError, self.schema.FOO.BAR.In, Parameter("names"))
-
+
# count=0 not allowed
self.assertRaises(DALError, Parameter,"names", 0)
-
+
# Mismatched count and len(items)
self.assertRaises(
DALError,
@@ -1579,6 +1596,23 @@
)
+ def test_tupleOfConstantsComparison(self):
+ """
+ For some reason Oracle requires multiple parentheses for comparisons.
+ """
+ self.assertEquals(
+ Select(
+ [self.schema.FOO.BAR],
+ From=self.schema.FOO,
+ Where=(Tuple([self.schema.FOO.BAR, self.schema.FOO.BAZ]) ==
+ Tuple([Constant(7), Constant(9)]))
+ ).toSQL(),
+ SQLFragment(
+ "select BAR from FOO where (BAR, BAZ) = ((?, ?))", [7, 9]
+ )
+ )
+
+
def test_oracleTableTruncation(self):
"""
L{Table}'s SQL generation logic will truncate table names if the dialect
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/internet/tcp.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/internet/tcp.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/internet/tcp.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -28,6 +28,7 @@
from twisted.application import internet
from twisted.internet import tcp, ssl
+from twisted.internet.defer import succeed
from twext.python.log import Logger
@@ -103,6 +104,24 @@
transport._startTLS()
return tcp.Port._preMakeConnection(self, transport)
+
+def _allConnectionsClosed(protocolFactory):
+ """
+ Check to see if protocolFactory implements allConnectionsClosed( ) and
+ if so, call it. Otherwise, return immediately.
+ This allows graceful shutdown by waiting for all requests to be completed.
+
+ @param protocolFactory: (usually) an HTTPFactory implementing allConnectionsClosed
+ which returns a Deferred which fires when all connections are closed.
+ @return: A Deferred firing None when all connections are closed, or immediately
+ if the given factory does not track its connections (e.g.
+ InheritingProtocolFactory)
+ """
+ if hasattr(protocolFactory, "allConnectionsClosed"):
+ return protocolFactory.allConnectionsClosed()
+ return succeed(None)
+
+
class MaxAcceptTCPServer(internet.TCPServer):
"""
TCP server which will uses MaxAcceptTCPPorts (and optionally,
@@ -114,7 +133,8 @@
def __init__(self, *args, **kwargs):
internet.TCPServer.__init__(self, *args, **kwargs)
- self.args[1].myServer = self
+ self.protocolFactory = self.args[1]
+ self.protocolFactory.myServer = self
self.inherit = self.kwargs.get("inherit", False)
self.backlog = self.kwargs.get("backlog", None)
self.interface = self.kwargs.get("interface", None)
@@ -131,6 +151,15 @@
self.myPort = port
return port
+ def stopService(self):
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ internet.TCPServer.stopService(self)
+ return _allConnectionsClosed(self.protocolFactory)
+
+
class MaxAcceptSSLServer(internet.SSLServer):
"""
SSL server which will uses MaxAcceptSSLPorts (and optionally,
@@ -139,7 +168,8 @@
def __init__(self, *args, **kwargs):
internet.SSLServer.__init__(self, *args, **kwargs)
- self.args[1].myServer = self
+ self.protocolFactory = self.args[1]
+ self.protocolFactory.myServer = self
self.inherit = self.kwargs.get("inherit", False)
self.backlog = self.kwargs.get("backlog", None)
self.interface = self.kwargs.get("interface", None)
@@ -156,3 +186,13 @@
self.myPort = port
return port
+ def stopService(self):
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ internet.SSLServer.stopService(self)
+ # TODO: check for an ICompletionWaiter interface
+ return _allConnectionsClosed(self.protocolFactory)
+
+
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/channel/http.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/channel/http.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/channel/http.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -32,6 +32,7 @@
from zope.interface import implements
from twisted.internet import interfaces, protocol, reactor
+from twisted.internet.defer import succeed, Deferred
from twisted.protocols import policies, basic
from twext.python.log import Logger
@@ -1019,6 +1020,7 @@
self.protocolArgs = kwargs
self.protocolArgs['requestFactory'] = requestFactory
self.connectedChannels = set()
+ self.allConnectionsClosedDeferred = None
def buildProtocol(self, addr):
@@ -1044,16 +1046,33 @@
"""
Remove a connected channel from the set of currently connected channels
and decrease the outstanding request count.
+ If someone is waiting for all the requests to be completed,
+ self.allConnectionsClosedDeferred will be non-None; fire that callback
+ when the number of outstanding requests hits zero.
"""
self.connectedChannels.remove(channel)
+ if self.allConnectionsClosedDeferred is not None:
+ if self.outstandingRequests == 0:
+ self.allConnectionsClosedDeferred.callback(None)
@property
def outstandingRequests(self):
return len(self.connectedChannels)
+ def allConnectionsClosed(self):
+ """
+ Return a Deferred that will fire when all outstanding requests have completed.
+ @return: A Deferred with a result of None
+ """
+ if self.outstandingRequests == 0:
+ return succeed(None)
+ self.allConnectionsClosedDeferred = Deferred()
+ return self.allConnectionsClosedDeferred
+
+
class HTTP503LoggingFactory (HTTPFactory):
"""
Factory for HTTP server which emits a 503 response when overloaded.
@@ -1201,7 +1220,7 @@
def removeConnectedChannel(self, channel):
"""
- Override L{HTTPFactory.addConnectedChannel} to resume listening on the
+ Override L{HTTPFactory.removeConnectedChannel} to resume listening on the
socket when there are too many outstanding channels.
"""
HTTPFactory.removeConnectedChannel(self, channel)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/metafd.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/metafd.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/metafd.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -81,14 +81,17 @@
def stopService(self):
"""
Stop reading on the inherited port.
+ @return: a Deferred which fires after the last outstanding request is complete.
"""
Service.stopService(self)
# XXX stopping should really be destructive, because otherwise we will
# always leak a file descriptor; i.e. this shouldn't be restartable.
- # XXX this needs to return a Deferred.
self.reportingFactory.inheritedPort.stopReading()
+ # Let any outstanding requests finish
+ return self.reportingFactory.allConnectionsClosed()
+
def createTransport(self, skt, peer, data, protocol):
"""
Create a TCP transport, from a socket object passed by the parent.
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/test/test_http.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/test/test_http.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twext/web2/test/test_http.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -14,7 +14,7 @@
from twisted.internet.defer import waitForDeferred, deferredGenerator
from twisted.protocols import loopback
from twisted.python import util, runtime
-from twext.web2.channel.http import SSLRedirectRequest
+from twext.web2.channel.http import SSLRedirectRequest, HTTPFactory
from twisted.internet.task import deferLater
class PreconditionTestCase(unittest.TestCase):
@@ -449,6 +449,45 @@
self.assertEquals(cxn.client.done, done)
+class GracefulShutdownTestCase(HTTPTests):
+
+ def _callback(self, result):
+ self.callbackFired = True
+
+ def testAllConnectionsClosedWithoutConnectedChannels(self):
+ """
+ allConnectionsClosed( ) should fire right away if no connected channels
+ """
+ self.callbackFired = False
+
+ factory = HTTPFactory(None)
+ factory.allConnectionsClosed().addCallback(self._callback)
+ self.assertTrue(self.callbackFired) # now!
+
+ def testallConnectionsClosedWithConnectedChannels(self):
+ """
+ allConnectionsClosed( ) should only fire after all connected channels
+ have been removed
+ """
+ self.callbackFired = False
+
+ factory = HTTPFactory(None)
+ factory.addConnectedChannel("A")
+ factory.addConnectedChannel("B")
+ factory.addConnectedChannel("C")
+
+ factory.allConnectionsClosed().addCallback(self._callback)
+
+ factory.removeConnectedChannel("A")
+ self.assertFalse(self.callbackFired) # wait for it...
+
+ factory.removeConnectedChannel("B")
+ self.assertFalse(self.callbackFired) # wait for it...
+
+ factory.removeConnectedChannel("C")
+ self.assertTrue(self.callbackFired) # now!
+
+
class CoreHTTPTestCase(HTTPTests):
# Note: these tests compare the client output using string
# matching. It is acceptable for this to change and break
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/directory/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/directory/util.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/directory/util.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -64,6 +64,8 @@
return value
+TRANSACTION_KEY = '_newStoreTransaction'
+
def transactionFromRequest(request, newStore):
"""
Return the associated transaction from the given HTTP request, creating a
@@ -85,7 +87,6 @@
@rtype: L{ITransaction} (and possibly L{ICalendarTransaction} and
L{IAddressBookTransaction} as well.
"""
- TRANSACTION_KEY = '_newStoreTransaction'
transaction = getattr(request, TRANSACTION_KEY, None)
if transaction is None:
transaction = newStore.newTransaction(repr(request))
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/localization.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/localization.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/localization.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -55,10 +55,8 @@
Before you can actually get translated text, you need to:
1) Choose a "domain" for your code, such as 'calendarserver'
- 2) Run pygettext.py on your source to generate a <domain>.pot file.
- pygettext.py scans the source for _( ) and copies those strings to the
- .pot.
- 3) For each language, copy the .pot file to .po and give it to the person
+ 2) Run xgettext on your source to generate a <domain>.po file.
+ 3) For each language, give the .po file to the person
who is doing the translation for editing
4) Run msgfmt.py on the translated .po to generate a binary .mo
5) Put the .mo into locales/<lang>/LC_MESSAGES/<domain>.mo
@@ -244,7 +242,7 @@
'hour24Number' : val.getHours(), # 0-23
'hour12Number' : hour12, # 1-12
'minuteNumber' : val.getMinutes(), # 0-59
- 'ampm' : _(ampm),
+ 'ampm' : ampm,
}
)
@@ -265,8 +263,8 @@
if days == 1:
parts.append(_("1 day"))
elif days > 1:
- parts.append(_("%(dayCount)d days" %
- { 'dayCount' : days }))
+ parts.append(_("%(dayCount)d days") %
+ { 'dayCount' : days })
hours = divmod(total / 3600, 24)[1]
minutes = divmod(total / 60, 60)[1]
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/put_common.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/method/put_common.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -751,9 +751,9 @@
Make sure sharees only use dropbox paths of the sharer.
"""
- # Only relevant if calendar is virtual share
+ # Only relevant if calendar is sharee collection
changed = False
- if self.destinationparent.isVirtualShare():
+ if self.destinationparent.isShareeCollection():
# Get all X-APPLE-DROPBOX's and ATTACH's that are http URIs
xdropboxes = self.calendar.getAllPropertiesInAnyComponent(
@@ -827,7 +827,7 @@
return changed
# Never add default alarms to calendar data in shared calendars
- if self.destinationparent.isVirtualShare():
+ if self.destinationparent.isShareeCollection():
return changed
# Add default alarm for VEVENT and VTODO only
@@ -942,8 +942,8 @@
if do_implicit_action and self.allowImplicitSchedule:
# Cannot do implicit in sharee's shared calendar
- isvirt = self.destinationparent.isVirtualShare()
- if isvirt:
+ isShareeCollection = self.destinationparent.isShareeCollection()
+ if isShareeCollection:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(calendarserver_namespace, "sharee-privilege-needed",),
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/resource.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -70,7 +70,7 @@
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.directory.internal import InternalDirectoryRecord
from twistedcaldav.extensions import DAVResource, DAVPrincipalResource, \
- PropertyNotFoundError, DAVResourceWithChildrenMixin
+ DAVResourceWithChildrenMixin
from twistedcaldav.ical import Component
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
@@ -525,29 +525,9 @@
"""
Need to special case schedule-calendar-transp for backwards compatability.
"""
-
- if type(property) is tuple:
- qname = property
- else:
- qname = property.qname()
-
- isvirt = self.isVirtualShare()
- if isvirt:
- if self.isShadowableProperty(qname):
- ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
- p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
- if p:
- returnValue(p)
-
- elif (not self.isGlobalProperty(qname)):
- result = (yield self._hasSharedProperty(qname, request))
- returnValue(result)
-
res = (yield self._hasGlobalProperty(property, request))
returnValue(res)
-
- @inlineCallbacks
def _hasSharedProperty(self, qname, request):
# Always have default alarms on shared calendars
@@ -557,13 +537,11 @@
caldavxml.DefaultAlarmVToDoDateTime.qname(),
caldavxml.DefaultAlarmVToDoDate.qname(),
) and self.isCalendarCollection():
- returnValue(True)
+ return True
- ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
- p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
- returnValue(p)
+ p = self.deadProperties().contains(qname)
+ return p
-
def _hasGlobalProperty(self, property, request):
"""
Need to special case schedule-calendar-transp for backwards compatability.
@@ -589,8 +567,6 @@
else:
qname = property.qname()
- isvirt = self.isVirtualShare()
-
if self.isCalendarCollection() or self.isAddressBookCollection():
# Push notification DAV property "pushkey"
@@ -602,7 +578,7 @@
if hasattr(self, "_newStoreObject"):
dataObject = getattr(self, "_newStoreObject")
if dataObject:
- label = "collection" if isvirt else "default"
+ label = "collection" if self.isShareeCollection() else "default"
nodeName = (yield dataObject.nodeName(label=label))
if nodeName:
propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
@@ -610,32 +586,16 @@
returnValue(customxml.PubSubXMPPPushKeyProperty())
- if isvirt:
- if self.isShadowableProperty(qname):
- ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
- try:
- p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
- returnValue(p)
- except PropertyNotFoundError:
- pass
-
- elif (not self.isGlobalProperty(qname)):
- result = (yield self._readSharedProperty(qname, request))
- returnValue(result)
-
res = (yield self._readGlobalProperty(qname, property, request))
returnValue(res)
- @inlineCallbacks
def _readSharedProperty(self, qname, request):
# Default behavior - read per-user dead property
- ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
- p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
- returnValue(p)
+ p = self.deadProperties().get(qname)
+ return p
-
@inlineCallbacks
def _readGlobalProperty(self, qname, property, request):
@@ -747,10 +707,10 @@
returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
elif qname == customxml.SharedURL.qname():
- isvirt = self.isVirtualShare()
+ isShareeCollection = self.isShareeCollection()
- if isvirt:
- returnValue(customxml.SharedURL(element.HRef.fromString(self._share.hosturl)))
+ if isShareeCollection:
+ returnValue(customxml.SharedURL(element.HRef.fromString(self._share.url())))
else:
returnValue(None)
@@ -764,14 +724,6 @@
"%r is not a WebDAVElement instance" % (property,)
)
- # Per-user Dav props currently only apply to a sharee's copy of a calendar
- isvirt = self.isVirtualShare()
- if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
- yield self._preProcessWriteProperty(property, request, isShare=True)
- ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
- p = self.deadProperties().set(property, uid=ownerPrincipal.principalUID())
- returnValue(p)
-
res = (yield self._writeGlobalProperty(property, request))
returnValue(res)
@@ -876,8 +828,8 @@
def accessControlList(self, request, *args, **kwargs):
acls = None
- isvirt = self.isVirtualShare()
- if isvirt:
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
acls = (yield self.shareeAccessControlList(request, *args, **kwargs))
if acls is None:
@@ -933,9 +885,9 @@
Return the DAV:owner property value (MUST be a DAV:href or None).
"""
- isVirt = self.isVirtualShare()
- if isVirt:
- parent = (yield self.locateParent(request, self._share.hosturl))
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
+ parent = (yield self.locateParent(request, self._share.url()))
else:
parent = (yield self.locateParent(request, request.urlForResource(self)))
if parent and isinstance(parent, CalDAVResource):
@@ -950,9 +902,9 @@
"""
Return the DAV:owner property value (MUST be a DAV:href or None).
"""
- isVirt = self.isVirtualShare()
- if isVirt:
- parent = (yield self.locateParent(request, self._share.hosturl))
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
+ parent = (yield self.locateParent(request, self._share.url()))
else:
parent = (yield self.locateParent(request, request.urlForResource(self)))
if parent and isinstance(parent, CalDAVResource):
@@ -968,14 +920,9 @@
This is the owner of the resource based on the URI used to access it. For a shared
collection it will be the sharee, otherwise it will be the regular the ownerPrincipal.
"""
-
- isVirt = self.isVirtualShare()
- if isVirt:
- returnValue(self._shareePrincipal)
- else:
- parent = (yield self.locateParent(
- request, request.urlForResource(self)
- ))
+ parent = (yield self.locateParent(
+ request, request.urlForResource(self)
+ ))
if parent and isinstance(parent, CalDAVResource):
result = (yield parent.resourceOwnerPrincipal(request))
returnValue(result)
@@ -1064,8 +1011,7 @@
"""
See L{ICalDAVResource.isSpecialCollection}.
"""
- if not self.isCollection():
- return False
+ if not self.isCollection(): return False
try:
resourcetype = self.resourceType()
@@ -1390,17 +1336,17 @@
"""
sharedParent = None
- isvirt = self.isVirtualShare()
- if isvirt:
- # A virtual share's quota root is the resource owner's root
- sharedParent = (yield request.locateResource(parentForURL(self._share.hosturl)))
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
+ # A sharee collection's quota root is the resource owner's root
+ sharedParent = (yield request.locateResource(parentForURL(self._share.url())))
else:
parent = (yield self.locateParent(request, request.urlForResource(self)))
if isCalendarCollectionResource(parent) or isAddressBookCollectionResource(parent):
- isvirt = parent.isVirtualShare()
- if isvirt:
- # A virtual share's quota root is the resource owner's root
- sharedParent = (yield request.locateResource(parentForURL(parent._share.hosturl)))
+ isShareeCollection = parent.isShareeCollection()
+ if isShareeCollection:
+ # A sharee collection's quota root is the resource owner's root
+ sharedParent = (yield request.locateResource(parentForURL(parent._share.url())))
if sharedParent:
result = (yield sharedParent.quotaRootResource(request))
@@ -2183,16 +2129,6 @@
return props
-
- def sharesDB(self):
- """
- Retrieve the new-style shares DB wrapper.
- """
- if not hasattr(self, "_sharesDB"):
- self._sharesDB = self._newStoreHome.retrieveOldShares()
- return self._sharesDB
-
-
def url(self):
return joinURL(self.parent.url(), self.name, "/")
@@ -2304,14 +2240,11 @@
self.putChild(name, child)
returnValue(child)
- # Try normal child type
+ # get regular or shared child
child = (yield self.makeRegularChild(name))
- # Try shares next if child does not exist
- if not child.exists() and self.canShare():
- sharedchild = yield self.provisionShare(name)
- if sharedchild:
- returnValue(sharedchild)
+ # add _share attribute if child is shared
+ yield self.provisionShare(child)
returnValue(child)
@@ -2344,7 +2277,6 @@
children = set(self._provisionedChildren.keys())
children.update(self._provisionedLinks.keys())
children.update((yield self._newStoreHome.listChildren()))
- children.update((yield self._newStoreHome.listSharedChildren()))
returnValue(children)
@@ -2679,6 +2611,9 @@
@inlineCallbacks
def makeRegularChild(self, name):
newCalendar = yield self._newStoreHome.calendarWithName(name)
+ if newCalendar and not newCalendar.owned() and not self.canShare():
+ newCalendar = None
+
from twistedcaldav.storebridge import CalendarCollectionResource
similar = CalendarCollectionResource(
newCalendar, self, name=name,
@@ -2865,7 +2800,7 @@
if defaultAddressBookProperty and len(defaultAddressBookProperty.children) == 1:
defaultAddressBook = str(defaultAddressBookProperty.children[0])
adbk = (yield request.locateResource(str(defaultAddressBook)))
- if adbk is not None and isAddressBookCollectionResource(adbk) and adbk.exists() and not adbk.isVirtualShare():
+ if adbk is not None and isAddressBookCollectionResource(adbk) and adbk.exists() and not adbk.isShareeCollection():
returnValue(defaultAddressBookProperty)
# Default is not valid - we have to try to pick one
@@ -2888,7 +2823,7 @@
if len(new_adbk) == 1:
adbkURI = str(new_adbk[0])
adbk = (yield request.locateResource(str(new_adbk[0])))
- if adbk is None or not adbk.exists() or not isAddressBookCollectionResource(adbk) or adbk.isVirtualShare():
+ if adbk is None or not adbk.exists() or not isAddressBookCollectionResource(adbk) or adbk.isShareeCollection():
# Validate that href's point to a valid addressbook.
raise HTTPError(ErrorResponse(
responsecode.CONFLICT,
@@ -2938,6 +2873,8 @@
mainCls = GlobalAddressBookCollectionResource
newAddressBook = yield self._newStoreHome.addressbookWithName(name)
+ if newAddressBook and not newAddressBook.owned() and not self.canShare():
+ newAddressBook = None
similar = mainCls(
newAddressBook, self, name,
principalCollections=self.principalCollections()
@@ -2955,13 +2892,17 @@
defaultAddressBookURL = joinURL(self.url(), "addressbook")
defaultAddressBook = (yield self.makeRegularChild("addressbook"))
if defaultAddressBook is None or not defaultAddressBook.exists():
- getter = iter((yield self._newStoreHome.addressbooks())) # These are only unshared children
+ addressbooks = yield self._newStoreHome.addressbooks()
+ ownedAddressBooks = [addressbook for addressbook in addressbooks if addressbook.owned()]
+ ownedAddressBooks.sort(key=lambda ab:ab.name())
+
+ # These are only unshared children
# FIXME: the back-end should re-provision a default addressbook here.
# Really, the dead property shouldn't be necessary, and this should
# be entirely computed by a back-end method like 'defaultAddressBook()'
try:
- anAddressBook = getter.next()
- except StopIteration:
+ anAddressBook = ownedAddressBooks[0]
+ except IndexError:
raise RuntimeError("No address books at all.")
defaultAddressBookURL = joinURL(self.url(), anAddressBook.name())
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/resource.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/resource.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -252,7 +252,7 @@
if defaultCalendarProperty and len(defaultCalendarProperty.children) == 1:
defaultCalendar = str(defaultCalendarProperty.children[0])
cal = (yield request.locateResource(str(defaultCalendar)))
- if cal is not None and isCalendarCollectionResource(cal) and cal.exists() and not cal.isVirtualShare():
+ if cal is not None and isCalendarCollectionResource(cal) and cal.exists() and not cal.isShareeCollection():
returnValue(defaultCalendarProperty)
# Default is not valid - we have to try to pick one
@@ -280,7 +280,7 @@
# TODO: check that owner of the new calendar is the same as owner of this inbox
if cal is None or not cal.exists() or not isCalendarCollectionResource(cal) or \
- cal.isVirtualShare() or not cal.isSupportedComponent(componentType):
+ cal.isShareeCollection() or not cal.isSupportedComponent(componentType):
# Validate that href's point to a valid calendar.
raise HTTPError(ErrorResponse(
responsecode.CONFLICT,
@@ -318,6 +318,8 @@
if calendarName == "inbox":
continue
calendar = (yield self.parent._newStoreHome.calendarWithName(calendarName))
+ if not calendar.owned():
+ continue
if not calendar.isSupportedComponent(componentType):
continue
break
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/scheduler.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/scheduler.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -26,7 +26,7 @@
from twistedcaldav.scheduling import addressmapping
from twistedcaldav.scheduling.cuaddress import LocalCalendarUser, \
InvalidCalendarUser, calendarUserFromPrincipal, RemoteCalendarUser
-from twistedcaldav.scheduling.scheduler import Scheduler
+from twistedcaldav.scheduling.scheduler import Scheduler, ScheduleResponseQueue
from txdav.xml import element as davxml
@@ -44,6 +44,8 @@
class CalDAVScheduler(Scheduler):
+ scheduleResponse = ScheduleResponseQueue
+
errorResponse = ErrorResponse
errorElements = {
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_resource.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_resource.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -375,7 +375,7 @@
self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
# Force the new calendar to think it is a virtual share
- newcalendar._isVirtualShare = True
+ newcalendar._isShareeCollection = True
try:
default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/scheduler.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/scheduler.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -23,7 +23,8 @@
from twistedcaldav.config import config
from twistedcaldav.scheduling import addressmapping
from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser
-from twistedcaldav.scheduling.scheduler import RemoteScheduler
+from twistedcaldav.scheduling.scheduler import RemoteScheduler, \
+ ScheduleResponseQueue
import itertools
import socket
@@ -40,6 +41,8 @@
class IMIPScheduler(RemoteScheduler):
+ scheduleResponse = ScheduleResponseQueue
+
errorResponse = ErrorResponse
errorElements = {
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -30,26 +30,25 @@
from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
+from twext.internet.gaiendpoint import GAIEndpoint
from twext.python.log import Logger
from twext.web2.dav.http import ErrorResponse
+from twistedcaldav.accounting import accountingEnabledForCategory, emitAccounting
from twistedcaldav.client.pool import _configuredClientContextFactory
-
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.config import config
+from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser, \
+ OtherServerCalendarUser
from twistedcaldav.scheduling.delivery import DeliveryService
+from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMUtils
+from twistedcaldav.scheduling.ischedule.remoteservers import IScheduleServerRecord
from twistedcaldav.scheduling.ischedule.remoteservers import IScheduleServers
-from twistedcaldav.scheduling.ischedule.remoteservers import IScheduleServerRecord
+from twistedcaldav.scheduling.ischedule.utils import lookupServerViaSRV
+from twistedcaldav.scheduling.ischedule.xml import ScheduleResponse, Response, \
+ RequestStatus, Recipient, ischedule_namespace
from twistedcaldav.scheduling.itip import iTIPRequestStatus
from twistedcaldav.util import utf8String, normalizationLookup
-from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser, \
- OtherServerCalendarUser
-from twext.internet.gaiendpoint import GAIEndpoint
-from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMUtils
-from twistedcaldav.config import config
-from twistedcaldav.scheduling.ischedule.utils import lookupServerViaSRV
from urlparse import urlsplit
-from twistedcaldav.accounting import accountingEnabledForCategory, emitAccounting
"""
Handles the sending of iSchedule scheduling messages. Used for both cross-domain scheduling,
@@ -104,7 +103,13 @@
# Lookup domain
result = (yield lookupServerViaSRV(domain))
if result is None:
- cls.domainServerMap[domain] = None
+ # Lookup domain
+ result = (yield lookupServerViaSRV(domain, service="_ischedule"))
+ if result is None:
+ cls.domainServerMap[domain] = None
+ else:
+ # Create the iSchedule server record for this server
+ cls.domainServerMap[domain] = IScheduleServerRecord(uri="http://%s:%s/.well-known/ischedule" % result)
else:
# Create the iSchedule server record for this server
cls.domainServerMap[domain] = IScheduleServerRecord(uri="https://%s:%s/.well-known/ischedule" % result)
@@ -135,7 +140,7 @@
# Cannot do server-to-server for this recipient.
err = HTTPError(ErrorResponse(
responsecode.NOT_FOUND,
- (caldav_namespace, "recipient-allowed"),
+ (ischedule_namespace, "recipient-allowed"),
"No server for recipient",
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_USER_SUPPORT)
@@ -147,7 +152,7 @@
# Cannot do server-to-server outgoing requests for this server.
err = HTTPError(ErrorResponse(
responsecode.NOT_FOUND,
- (caldav_namespace, "recipient-allowed"),
+ (ischedule_namespace, "recipient-allowed"),
"Cannot send to recipient's server",
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE)
@@ -263,7 +268,7 @@
for recipient in self.recipients:
err = HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
- (caldav_namespace, "recipient-failed"),
+ (ischedule_namespace, "recipient-failed"),
"Server-to-server request failed",
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE)
@@ -449,17 +454,17 @@
# Check for correct root element
schedule_response = xml.root_element
- if not isinstance(schedule_response, caldavxml.ScheduleResponse) or not schedule_response.children:
+ if not isinstance(schedule_response, ScheduleResponse) or not schedule_response.children:
raise HTTPError(responsecode.BAD_REQUEST)
# Parse each response - do this twice: once looking for errors that will
# result in all recipients shown as failures; the second loop adds all the
# valid responses to the actual result.
for response in schedule_response.children:
- if not isinstance(response, caldavxml.Response) or not response.children:
+ if not isinstance(response, Response) or not response.children:
raise HTTPError(responsecode.BAD_REQUEST)
- recipient = response.childOfType(caldavxml.Recipient)
- request_status = response.childOfType(caldavxml.RequestStatus)
+ recipient = response.childOfType(Recipient)
+ request_status = response.childOfType(RequestStatus)
if not recipient or not request_status:
raise HTTPError(responsecode.BAD_REQUEST)
for response in schedule_response.children:
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -233,7 +233,8 @@
value = value[:pos] + value[pos + len(remove_b):]
value = " ".join(value.split())
- return "%s:%s\r\n" % (name, value,)
+ crlf = "" if name == DKIM_SIGNATURE.lower() else "\r\n"
+ return "%s:%s%s" % (name, value, crlf)
@staticmethod
@@ -331,12 +332,12 @@
self.hash_func = DKIMUtils.hash_func(self.algorithm)
self.keyMethods = []
+ if usePrivateExchangeKey:
+ self.keyMethods.append(Q_PRIVATE)
+ if useHTTPKey:
+ self.keyMethods.append(Q_HTTP)
if useDNSKey:
self.keyMethods.append(Q_DNS)
- if useHTTPKey:
- self.keyMethods.append(Q_HTTP)
- if usePrivateExchangeKey:
- self.keyMethods.append(Q_PRIVATE)
self.message_id = str(uuid.uuid4())
@@ -403,7 +404,7 @@
for name in self.sign_headers:
oversign = name[-1] == "+"
name = name.rstrip("+")
- for value in raw.get(name.lower(), ()):
+ for value in reversed(raw.get(name.lower(), ())):
headers.append(DKIMUtils.canonicalizeHeader(name, value))
sign_headers.append(name)
if oversign:
@@ -645,7 +646,7 @@
actual_headers = self.request.headers.getRawHeaders(header)
if actual_headers:
try:
- headers.append((header, actual_headers[header_counter[header]],))
+ headers.append((header, actual_headers[-1 - header_counter[header]],))
except IndexError:
pass
header_counter[header] += 1
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -23,7 +23,8 @@
from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser
from twistedcaldav.scheduling.cuaddress import calendarUserFromPrincipal
from twistedcaldav.scheduling.ischedule.remoteservers import IScheduleServers
-from twistedcaldav.scheduling.scheduler import RemoteScheduler
+from twistedcaldav.scheduling.scheduler import RemoteScheduler, \
+ ScheduleResponseQueue
import twistedcaldav.scheduling.ischedule.xml as ixml
from twistedcaldav.scheduling.ischedule.localservers import Servers
from twistedcaldav.util import normalizationLookup
@@ -39,6 +40,7 @@
from twistedcaldav.scheduling.ischedule.xml import ischedule_namespace
from txdav.xml.base import WebDAVUnknownElement
from twistedcaldav.scheduling.ischedule.utils import getIPsFromHost
+from twistedcaldav.scheduling.ischedule import xml
"""
L{IScheduleScheduler} - handles deliveries for scheduling messages being POSTed to the iSchedule inbox.
@@ -95,8 +97,27 @@
+class IScheduleResponseQueue (ScheduleResponseQueue):
+ """
+ Stores a list of (typically error) responses for use in a
+ L{ScheduleResponse}.
+ """
+
+ schedule_response_element = xml.ScheduleResponse
+ response_element = xml.Response
+ recipient_element = xml.Recipient
+ recipient_uses_href = False
+ request_status_element = xml.RequestStatus
+ error_element = xml.Error
+ response_description_element = xml.ResponseDescription
+ calendar_data_element = xml.CalendarData
+
+
+
class IScheduleScheduler(RemoteScheduler):
+ scheduleResponse = IScheduleResponseQueue
+
errorResponse = ErrorResponse
errorElements = {
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -33,6 +33,7 @@
class QueryResult (WebDAVElement):
namespace = ischedule_namespace
name = "query-result"
+
allowed_children = {
(ischedule_namespace, "capability-set"): (0, None),
}
@@ -43,6 +44,7 @@
class Capabilities (WebDAVElement):
namespace = ischedule_namespace
name = "capabilities"
+
allowed_children = {
(ischedule_namespace, "versions"): (1, 1),
(ischedule_namespace, "scheduling-messages"): (1, 1),
@@ -63,6 +65,7 @@
class Versions (WebDAVElement):
namespace = ischedule_namespace
name = "versions"
+
allowed_children = {
(ischedule_namespace, "version"): (1, None),
}
@@ -80,6 +83,7 @@
class SchedulingMessages (WebDAVElement):
namespace = ischedule_namespace
name = "scheduling-messages"
+
allowed_children = {
(ischedule_namespace, "component"): (1, None),
}
@@ -90,6 +94,7 @@
class Component (WebDAVElement):
namespace = ischedule_namespace
name = "component"
+
allowed_children = {
(ischedule_namespace, "method"): (0, None),
}
@@ -101,6 +106,7 @@
class Method (WebDAVEmptyElement):
namespace = ischedule_namespace
name = "method"
+
allowed_attributes = {"name": True}
@@ -109,6 +115,7 @@
class CalendarDataTypes (WebDAVElement):
namespace = ischedule_namespace
name = "calendar-data-types"
+
allowed_children = {
(ischedule_namespace, "calendar-data-type"): (1, None),
}
@@ -119,6 +126,7 @@
class CalendarDataType (WebDAVTextElement):
namespace = ischedule_namespace
name = "calendar-data-type"
+
allowed_attributes = {
"content-type": True,
"version": True,
@@ -130,6 +138,7 @@
class Attachments (WebDAVElement):
namespace = ischedule_namespace
name = "attachments"
+
allowed_children = {
(ischedule_namespace, "inline"): (0, 1),
(ischedule_namespace, "external"): (0, 1),
@@ -194,6 +203,53 @@
@registerElement
+class ScheduleResponse (WebDAVTextElement):
+ namespace = ischedule_namespace
+ name = "schedule-response"
+
+ allowed_children = {
+ (ischedule_namespace, "response"): (0, None),
+ }
+
+
+
+ at registerElement
+class Response (WebDAVElement):
+ namespace = ischedule_namespace
+ name = "response"
+
+ allowed_children = {
+ (ischedule_namespace, "recipient"): (1, 1),
+ (ischedule_namespace, "request-status"): (1, 1),
+ (ischedule_namespace, "calendar-data"): (0, 1),
+ (ischedule_namespace, "error"): (0, 1),
+ (ischedule_namespace, "response-description"): (0, 1),
+ }
+
+
+
+ at registerElement
+class Recipient (WebDAVTextElement):
+ namespace = ischedule_namespace
+ name = "recipient"
+
+
+
+ at registerElement
+class RequestStatus (WebDAVTextElement):
+ namespace = ischedule_namespace
+ name = "request-status"
+
+
+
+ at registerElement
+class CalendarData (WebDAVTextElement):
+ namespace = ischedule_namespace
+ name = "calendar-data"
+
+
+
+ at registerElement
class Error (WebDAVElement):
namespace = ischedule_namespace
name = "error"
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/scheduler.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/scheduler.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -22,7 +22,7 @@
from twext.web2.http import HTTPError, Response, StatusResponse
from twext.web2.http_headers import MimeType
from txdav.xml import element as davxml
-from twext.web2.dav.http import errorForFailure, messageForFailure, statusForFailure, \
+from twext.web2.dav.http import messageForFailure, statusForFailure, \
ErrorResponse
from twistedcaldav import caldavxml
@@ -114,6 +114,8 @@
class Scheduler(object):
+ scheduleResponse = None
+
errorResponse = None # The class used for generating an HTTP XML error response
errorElements = {
@@ -561,7 +563,7 @@
freebusy = self.checkForFreeBusy()
# Prepare for multiple responses
- responses = ScheduleResponseQueue(self.method, responsecode.OK)
+ responses = self.scheduleResponse(self.method, responsecode.OK)
# Loop over each recipient and aggregate into lists by service types.
caldav_recipients = []
@@ -751,7 +753,7 @@
ScheduleResponse L{Response} object.
Renders itself as a CalDAV:schedule-response XML document.
"""
- def __init__(self, xml_responses, location=None):
+ def __init__(self, schedule_response_element, xml_responses, location=None):
"""
@param xml_responses: an iterable of davxml.Response objects.
@param location: the value of the location header to return in the response,
@@ -759,7 +761,7 @@
"""
Response.__init__(self, code=responsecode.OK,
- stream=caldavxml.ScheduleResponse(*xml_responses).toxml())
+ stream=schedule_response_element(*xml_responses).toxml())
self.headers.setHeader("content-type", MimeType("text", "xml"))
@@ -773,6 +775,16 @@
Stores a list of (typically error) responses for use in a
L{ScheduleResponse}.
"""
+
+ schedule_response_element = caldavxml.ScheduleResponse
+ response_element = caldavxml.Response
+ recipient_element = caldavxml.Recipient
+ recipient_uses_href = True
+ request_status_element = caldavxml.RequestStatus
+ error_element = davxml.Error
+ response_description_element = davxml.ResponseDescription
+ calendar_data_element = caldavxml.CalendarData
+
def __init__(self, method, success_response):
"""
@param method: the name of the method generating the queue.
@@ -810,7 +822,7 @@
message = responsecode.RESPONSES[code]
elif isinstance(what, Failure):
code = statusForFailure(what)
- error = errorForFailure(what)
+ error = self.errorForFailure(what)
message = messageForFailure(what)
else:
raise AssertionError("Unknown data type: %r" % (what,))
@@ -819,17 +831,24 @@
self.log_error("Error during %s for %s: %s" % (self.method, recipient, message))
children = []
- children.append(caldavxml.Recipient(davxml.HRef.fromString(recipient)))
- children.append(caldavxml.RequestStatus(reqstatus))
+ children.append(self.recipient_element(davxml.HRef.fromString(recipient)) if self.recipient_uses_href else self.recipient_element.fromString(recipient))
+ children.append(self.request_status_element(reqstatus))
if calendar is not None:
- children.append(caldavxml.CalendarData.fromCalendar(calendar))
+ children.append(self.calendar_data_element.fromCalendar(calendar))
if error is not None:
children.append(error)
if message is not None:
- children.append(davxml.ResponseDescription(message))
- self.responses.append(caldavxml.Response(*children))
+ children.append(self.response_description_element(message))
+ self.responses.append(self.response_element(*children))
+ def errorForFailure(self, failure):
+ if failure.check(HTTPError) and isinstance(failure.value.response, ErrorResponse):
+ return self.error_element.Error(failure.value.response.error)
+ else:
+ return None
+
+
def clone(self, clone):
"""
Add a response cloned from an existing caldavxml.Response element.
@@ -838,11 +857,11 @@
if not isinstance(clone, caldavxml.Response):
raise AssertionError("Incorrect element type: %r" % (clone,))
- recipient = clone.childOfType(caldavxml.Recipient)
- request_status = clone.childOfType(caldavxml.RequestStatus)
- calendar_data = clone.childOfType(caldavxml.CalendarData)
- error = clone.childOfType(davxml.Error)
- desc = clone.childOfType(davxml.ResponseDescription)
+ recipient = clone.childOfType(self.recipient_element)
+ request_status = clone.childOfType(self.request_status_element)
+ calendar_data = clone.childOfType(self.calendar_data_element)
+ error = clone.childOfType(self.error_element)
+ desc = clone.childOfType(self.response_description_element)
children = []
children.append(recipient)
@@ -853,7 +872,7 @@
children.append(error)
if desc is not None:
children.append(desc)
- self.responses.append(caldavxml.Response(*children))
+ self.responses.append(self.response_element(*children))
def response(self):
@@ -864,6 +883,6 @@
@return: the response.
"""
if self.responses:
- return ScheduleResponseResponse(self.responses, self.location)
+ return ScheduleResponseResponse(self.schedule_response_element, self.responses, self.location)
else:
return self.success_response
Deleted: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharedcollection.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharedcollection.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharedcollection.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,62 +0,0 @@
-##
-# Copyright (c) 2010-2012 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__ = [
- "SharedCollectionResource",
-]
-
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2 import responsecode
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-from twistedcaldav.linkresource import LinkResource
-
-
-"""
-Sharing behavior
-"""
-
-class SharedCollectionResource(LinkResource):
- """
- This is similar to a LinkResource except that we locate our shared collection resource dynamically.
- """
-
- def __init__(self, parent, share):
- self.share = share
- super(SharedCollectionResource, self).__init__(parent, self.share.hosturl)
-
- @inlineCallbacks
- def linkedResource(self, request):
- """
- Resolve the share host url to the underlying resource (set to be a virtual share).
- """
-
- if not hasattr(self, "_linkedResource"):
- self._linkedResource = (yield request.locateResource(self.share.hosturl))
-
- if self._linkedResource is not None:
- # FIXME: this is awkward - because we are "mutating" this object into a virtual share
- # we must not cache the resource at this URL, otherwise an access of the owner's resource
- # will return the same virtually shared one which would be wrong.
- request._forgetResource(self._linkedResource, self.share.hosturl)
-
- ownerPrincipal = (yield self.parent.ownerPrincipal(request))
- self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
- else:
- raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "Missing link target: %s" % (self.linkURL,)))
-
- returnValue(self._linkedResource)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharing.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/sharing.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -30,6 +30,10 @@
from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
from twext.web2.dav.resource import TwistedACLInheritable
from twext.web2.dav.util import allDataFromStream, joinURL
+from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
+ _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_STATUS_INVITED, \
+ _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, \
+ _BIND_STATUS_INVALID
from txdav.xml import element
from twisted.internet.defer import succeed, inlineCallbacks, DeferredList, \
@@ -40,19 +44,22 @@
from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
from twistedcaldav.linkresource import LinkFollowerMixIn
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
from pycalendar.datetime import PyCalendarDateTime
-from uuid import uuid4
import os
import types
-# Types of sharing mode
-SHARETYPE_INVITE = "I" # Invite based sharing
-SHARETYPE_DIRECT = "D" # Direct linking based sharing
+# FIXME: Get rid of these imports
+from twistedcaldav.directory.util import TRANSACTION_KEY
+# circular import
+#from txdav.common.datastore.sql import ECALENDARTYPE, EADDRESSBOOKTYPE
+ECALENDARTYPE = 0
+EADDRESSBOOKTYPE = 1
+#ENOTIFICATIONTYPE = 2
+
class SharedCollectionMixin(object):
@inlineCallbacks
@@ -63,20 +70,33 @@
"""
if config.Sharing.Enabled:
+ def invitePropertyElement(invitation, includeUID=True):
+
+ userid = "urn:uuid:" + invitation.shareeUID()
+ principal = self.principalForUID(invitation.shareeUID())
+ cn = principal.displayName() if principal else invitation.shareeUID()
+ return customxml.InviteUser(
+ customxml.UID.fromString(invitation.uid()) if includeUID else None,
+ element.HRef.fromString(userid),
+ customxml.CommonName.fromString(cn),
+ customxml.InviteAccess(invitationAccessMapToXML[invitation.access()]()),
+ invitationStatusMapToXML[invitation.state()](),
+ )
+
# See if this property is on the shared calendar
isShared = yield self.isShared(request)
if isShared:
yield self.validateInvites(request)
- records = yield self.invitesDB().allRecords()
+ invitations = yield self._allInvitations()
returnValue(customxml.Invite(
- *[record.makePropertyElement() for record in records]
+ *[invitePropertyElement(invitation) for invitation in invitations]
))
# See if it is on the sharee calendar
- if self.isVirtualShare():
- original = (yield request.locateResource(self._share.hosturl))
+ if self.isShareeCollection():
+ original = (yield request.locateResource(self._share.url()))
yield original.validateInvites(request)
- records = yield original.invitesDB().allRecords()
+ invitations = yield original._allInvitations()
ownerPrincipal = (yield original.ownerPrincipal(request))
owner = ownerPrincipal.principalURL()
@@ -87,7 +107,7 @@
element.HRef.fromString(owner),
customxml.CommonName.fromString(ownerCN),
),
- *[record.makePropertyElement(includeUID=False) for record in records]
+ *[invitePropertyElement(invitation, includeUID=False) for invitation in invitations]
))
returnValue(None)
@@ -101,10 +121,6 @@
rtype = element.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
self.writeDeadProperty(rtype)
- # Create invites database
- self.invitesDB().create()
-
-
@inlineCallbacks
def downgradeFromShare(self, request):
@@ -115,18 +131,14 @@
self.writeDeadProperty(rtype)
# Remove all invitees
- for record in (yield self.invitesDB().allRecords()):
- yield self.uninviteRecordFromShare(record, request)
+ for invitation in (yield self._allInvitations()):
+ yield self.uninviteFromShare(invitation, request)
- # Remove invites database
- self.invitesDB().remove()
- delattr(self, "_invitesDB")
-
returnValue(True)
@inlineCallbacks
- def changeUserInviteState(self, request, inviteUID, principalURL, state, summary=None):
+ def changeUserInviteState(self, request, inviteUID, shareeUID, state, summary=None):
shared = (yield self.isShared(request))
if not shared:
raise HTTPError(ErrorResponse(
@@ -135,9 +147,8 @@
"Invalid share",
))
- principalUID = principalURL.split("/")[3]
- record = yield self.invitesDB().recordForInviteUID(inviteUID)
- if record is None or record.principalUID != principalUID:
+ invitation = yield self._invitationForUID(inviteUID)
+ if invitation is None or invitation.shareeUID() != shareeUID:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
@@ -145,11 +156,8 @@
))
# Only certain states are sharer controlled
- if record.state in ("NEEDS-ACTION", "ACCEPTED", "DECLINED",):
- record.state = state
- if summary is not None:
- record.summary = summary
- yield self.invitesDB().addOrUpdateRecord(record)
+ if invitation.state() in ("NEEDS-ACTION", "ACCEPTED", "DECLINED",):
+ yield self._updateInvitation(invitation, state=state, summary=summary)
@inlineCallbacks
@@ -180,11 +188,11 @@
(calendarserver_namespace, "valid-principal"),
"Current user principal not specified",
))
- principal = (yield request.locateResource(principalURL))
+ sharee = (yield request.locateResource(principalURL))
# Check enabled for service
from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
- if not isinstance(principal, DirectoryCalendarPrincipalResource):
+ if not isinstance(sharee, DirectoryCalendarPrincipalResource):
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(calendarserver_namespace, "invalid-principal"),
@@ -193,9 +201,9 @@
# Get the home collection
if self.isCalendarCollection():
- home = yield principal.calendarHome(request)
+ shareeHomeResource = yield sharee.calendarHome(request)
elif self.isAddressBookCollection():
- home = yield principal.addressBookHome(request)
+ shareeHomeResource = yield sharee.addressBookHome(request)
else:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
@@ -204,9 +212,9 @@
))
# TODO: Make sure principal is not sharing back to themselves
- compareURL = (yield self.canonicalURL(request))
- homeURL = home.url()
- if compareURL.startswith(homeURL):
+ hostURL = (yield self.canonicalURL(request))
+ shareeHomeURL = shareeHomeResource.url()
+ if hostURL.startswith(shareeHomeURL):
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(calendarserver_namespace, "invalid-share"),
@@ -214,8 +222,8 @@
))
# Accept it
- directID = home.sharesDB().directShareID(home, self)
- response = (yield home.acceptDirectShare(request, compareURL, directID, self.displayName()))
+ directUID = Share.directUID(shareeHomeResource._newStoreHome, self._newStoreObject)
+ response = (yield shareeHomeResource.acceptDirectShare(request, hostURL, directUID, self.displayName()))
# Return the URL of the shared calendar
returnValue(response)
@@ -227,29 +235,26 @@
returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
- def setVirtualShare(self, shareePrincipal, share):
- self._isVirtualShare = True
- self._shareePrincipal = shareePrincipal
+ def setShare(self, share):
+ self._isShareeCollection = True # _isShareeCollection attr is used by self tests
self._share = share
- if hasattr(self, "_newStoreObject"):
- self._newStoreObject.setSharingUID(self._shareePrincipal.principalUID())
+ def isShareeCollection(self):
+ """ Return True if this is a sharee view of a shared calendar collection """
+ return hasattr(self, "_isShareeCollection")
- def isVirtualShare(self):
- """ Return True if this is a shared calendar collection """
- return hasattr(self, "_isVirtualShare")
-
@inlineCallbacks
- def removeVirtualShare(self, request):
- """ Return True if this is a shared calendar collection """
+ def removeShareeCollection(self, request):
+ sharee = self.principalForUID(self._share.shareeUID())
+
# Remove from sharee's calendar/address book home
if self.isCalendarCollection():
- shareeHome = yield self._shareePrincipal.calendarHome(request)
+ shareeHome = yield sharee.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = yield self._shareePrincipal.addressBookHome(request)
+ shareeHome = yield sharee.addressBookHome(request)
returnValue((yield shareeHome.removeShare(request, self._share)))
@@ -262,8 +267,8 @@
else:
rtype = superMethod()
- isVirt = self.isVirtualShare()
- if isVirt:
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
rtype = element.ResourceType(
*(
tuple([child for child in rtype.children if child.qname() != customxml.SharedOwner.qname()]) +
@@ -289,50 +294,46 @@
@inlineCallbacks
def shareeAccessControlList(self, request, *args, **kwargs):
- assert self._isVirtualShare, "Only call this for a virtual share"
+ assert self._isShareeCollection, "Only call this for a sharee collection"
wikiAccessMethod = kwargs.get("wikiAccessMethod", getWikiAccess)
+ sharee = self.principalForUID(self._share.shareeUID())
+
# Direct shares use underlying privileges of shared collection
- if self._share.sharetype == SHARETYPE_DIRECT:
- original = (yield request.locateResource(self._share.hosturl))
+ if self._share.direct():
+ original = (yield request.locateResource(self._share.url()))
owner = yield original.ownerPrincipal(request)
if owner.record.recordType == WikiDirectoryService.recordType_wikis:
# Access level comes from what the wiki has granted to the
# sharee
- userID = self._shareePrincipal.record.guid
+ userID = sharee.record.guid
wikiID = owner.record.shortNames[0]
- inviteAccess = (yield wikiAccessMethod(userID, wikiID))
- if inviteAccess == "read":
- inviteAccess = "read-only"
- elif inviteAccess in ("write", "admin"):
- inviteAccess = "read-write"
+ access = (yield wikiAccessMethod(userID, wikiID))
+ if access == "read":
+ access = "read-only"
+ elif access in ("write", "admin"):
+ access = "read-write"
else:
- inviteAccess = None
+ access = None
else:
result = (yield original.accessControlList(request, *args,
**kwargs))
returnValue(result)
else:
- # Invite shares use access mode from the invite
+ # Invited shares use access mode from the invite
+ # Get the access for self
+ access = Invitation(self._newStoreObject).access()
- # Get the invite for this sharee
- invite = yield self.invitesDB().recordForInviteUID(
- self._share.shareuid
- )
- if invite is None:
- returnValue(element.ACL())
- inviteAccess = invite.access
-
userprivs = [
]
- if inviteAccess in ("read-only", "read-write", "read-write-schedule",):
+ if access in ("read-only", "read-write",):
userprivs.append(element.Privilege(element.Read()))
userprivs.append(element.Privilege(element.ReadACL()))
userprivs.append(element.Privilege(element.ReadCurrentUserPrivilegeSet()))
- if inviteAccess in ("read-only",):
+ if access in ("read-only",):
userprivs.append(element.Privilege(element.WriteProperties()))
- if inviteAccess in ("read-write", "read-write-schedule",):
+ if access in ("read-write",):
userprivs.append(element.Privilege(element.Write()))
proxyprivs = list(userprivs)
try:
@@ -344,7 +345,7 @@
aces = (
# Inheritable specific access for the resource's associated principal.
element.ACE(
- element.Principal(element.HRef(self._shareePrincipal.principalURL())),
+ element.Principal(element.HRef(sharee.principalURL())),
element.Grant(*userprivs),
element.Protected(),
TwistedACLInheritable(),
@@ -371,7 +372,7 @@
aces += (
# DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
element.ACE(
- element.Principal(element.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-read/"))),
+ element.Principal(element.HRef(joinURL(sharee.principalURL(), "calendar-proxy-read/"))),
element.Grant(
element.Privilege(element.Read()),
element.Privilege(element.ReadCurrentUserPrivilegeSet()),
@@ -381,7 +382,7 @@
),
# DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
element.ACE(
- element.Principal(element.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-write/"))),
+ element.Principal(element.HRef(joinURL(sharee.principalURL(), "calendar-proxy-write/"))),
element.Grant(*proxyprivs),
element.Protected(),
TwistedACLInheritable(),
@@ -392,7 +393,7 @@
@inlineCallbacks
- def validUserIDForShare(self, userid, request):
+ def validUserIDForShare(self, userid, request=None):
"""
Test the user id to see if it is a valid identifier for sharing and
return a "normalized" form for our own use (e.g. convert mailto: to
@@ -408,10 +409,11 @@
# First try to resolve as a principal
principal = self.principalForCalendarUserAddress(userid)
if principal:
- ownerPrincipal = (yield self.ownerPrincipal(request))
- owner = ownerPrincipal.principalURL()
- if owner == principal.principalURL():
- returnValue(None)
+ if request:
+ ownerPrincipal = (yield self.ownerPrincipal(request))
+ owner = ownerPrincipal.principalURL()
+ if owner == principal.principalURL():
+ returnValue(None)
returnValue(principal.principalURL())
# TODO: we do not support external users right now so this is being hard-coded
@@ -422,48 +424,21 @@
returnValue(None)
- def validUserIDWithCommonNameForShare(self, userid, cn):
- """
- Validate user ID and find the common name.
-
- @param userid: the userid to test
- @type userid: C{str}
- @param cn: default common name to use if principal has none
- @type cn: C{str}
-
- @return: C{tuple} of C{str} of normalized userid or C{None} if
- userid is not allowed, and appropriate common name.
- """
-
- # First try to resolve as a principal
- principal = self.principalForCalendarUserAddress(userid)
- if principal:
- return userid, principal.principalURL(), principal.displayName()
-
- # TODO: we do not support external users right now so this is being hard-coded
- # off in spite of the config option.
- #elif config.Sharing.AllowExternalUsers:
- # return userid, None, cn
- else:
- return None, None, None
-
-
@inlineCallbacks
def validateInvites(self, request):
"""
Make sure each userid in an invite is valid - if not re-write status.
"""
+ #assert request
+ invitations = yield self._allInvitations()
+ for invitation in invitations:
+ if invitation.state() != "INVALID":
+ if not (yield self.validUserIDForShare("urn:uuid:" + invitation.shareeUID(), request)):
+ yield self._updateInvitation(invitation, state="INVALID")
- records = yield self.invitesDB().allRecords()
- for record in records:
- uid = (yield self.validUserIDForShare(record.userid, request))
- if uid is None and record.state != "INVALID":
- record.state = "INVALID"
- yield self.invitesDB().addOrUpdateRecord(record)
+ returnValue(len(invitations))
- returnValue(len(records))
-
def inviteUserToShare(self, userid, cn, ace, summary, request):
""" Send out in invite first, and then add this user to the share list
@param userid:
@@ -520,76 +495,103 @@
@inlineCallbacks
- def _createLock(self, userid, request):
+ def _createInvitation(self, shareeUID, access, summary,):
+ '''
+ Create a new homeChild and wrap it in an Invitation
+ '''
+ if self.isCalendarCollection():
+ shareeHome = yield self._newStoreObject._txn.calendarHomeWithUID(shareeUID, create=True)
+ elif self.isAddressBookCollection():
+ shareeHome = yield self._newStoreObject._txn.addressbookHomeWithUID(shareeUID, create=True)
+
+ sharedName = yield self._newStoreObject.shareWith(shareeHome,
+ mode=invitationAccessToBindModeMap[access],
+ status=_BIND_STATUS_INVITED,
+ message=summary)
+
+ shareeHomeChild = yield shareeHome.invitedChildWithName(sharedName)
+ invitation = Invitation(shareeHomeChild)
+ returnValue(invitation)
+
+ @inlineCallbacks
+ def _updateInvitation(self, invitation, access=None, state=None, summary=None):
+ mode = None if access is None else invitationAccessToBindModeMap[access]
+ status = None if state is None else invitationStateToBindStatusMap[state]
+
+ yield self._newStoreObject.updateShare(invitation._shareeHomeChild, mode=mode, status=status, message=summary)
+ assert not access or access == invitation.access(), "access=%s != invitation.access()=%s" % (access, invitation.access())
+ assert not state or state == invitation.state(), "state=%s != invitation.state()=%s" % (state, invitation.state())
+ assert not summary or summary == invitation.summary(), "summary=%s != invitation.summary()=%s" % (summary, invitation.summary())
+
+
+ @inlineCallbacks
+ def _allInvitations(self, includeAccepted=True):
"""
- Create an instance of MemcacheLock whose key is based on the sharee's
- uid and the collection's URL
+ Get list of all invitations to this object
+
+ For legacy reasons, all invitations are all invited + shared (accepted, not direct).
+ Combine these two into a single sorted list so code is similar to that for legacy invite db
"""
- returnValue(MemcacheLock(
- "ShareInviteLock",
- (yield self._lockToken(userid, request)),
- timeout=config.Scheduling.Options.UIDLockTimeoutSeconds,
- expire_time=config.Scheduling.Options.UIDLockExpirySeconds,
- ))
+ invitedHomeChildren = yield self._newStoreObject.asInvited()
+ if includeAccepted:
+ acceptedHomeChildren = yield self._newStoreObject.asShared()
+ # remove direct shares (it might be OK not to remove these, that would be different from legacy code)
+ indirectAccceptedHomeChildren = [homeChild for homeChild in acceptedHomeChildren
+ if homeChild.shareMode() != _BIND_MODE_DIRECT]
+ invitedHomeChildren += indirectAccceptedHomeChildren
+ invitations = [Invitation(homeChild) for homeChild in invitedHomeChildren]
+ invitations.sort(key=lambda invitation:invitation.shareeUID())
+ returnValue(invitations)
+
@inlineCallbacks
- def _acquireLock(self, lock):
+ def _invitationForShareeUID(self, shareeUID, includeAccepted=True):
"""
- Attempt to acquire a lock -- can raise MemcacheLockTimeoutError
+ Get an invitation for this sharee principal UID
"""
- try:
- yield lock.acquire()
- except MemcacheLockTimeoutError:
- self.log_error("Memcache lock timeout for sharing invite")
- raise
+ invitations = yield self._allInvitations(includeAccepted=includeAccepted)
+ for invitation in invitations:
+ if invitation.shareeUID() == shareeUID:
+ returnValue(invitation)
+ returnValue(None)
@inlineCallbacks
- def _lockToken(self, userid, request):
+ def _invitationForUID(self, uid, includeAccepted=True):
"""
- Generate a string we can use for a memcache lock key
+ Get an invitation for an invitations uid
"""
- hosturl = (yield self.canonicalURL(request))
- returnValue("%s:%s" % (hosturl, userid))
+ invitations = yield self._allInvitations(includeAccepted=includeAccepted)
+ for invitation in invitations:
+ if invitation.uid() == uid:
+ returnValue(invitation)
+ returnValue(None)
+
@inlineCallbacks
def inviteSingleUserToShare(self, userid, cn, ace, summary, request):
- # Validate userid and cn
- userid, principalURL, cn = self.validUserIDWithCommonNameForShare(userid, cn)
-
# We currently only handle local users
- if principalURL is None:
+ sharee = self.principalForCalendarUserAddress(userid)
+ if not sharee:
returnValue(False)
- # Acquire a memcache lock based on collection URL and sharee UID
- # TODO: when sharing moves into the store this should be replaced
- # by DB-level locking
- lock = (yield self._createLock(userid, request))
- yield self._acquireLock(lock)
+ shareeUID = sharee.principalUID()
- try:
- # Look for existing invite and update its fields or create new one
- principalUID = principalURL.split("/")[3]
- record = yield self.invitesDB().recordForPrincipalUID(principalUID)
- if record:
- record.name = cn
- record.access = inviteAccessMapFromXML[type(ace)]
- record.summary = summary
- else:
- record = Invite(str(uuid4()), userid, principalUID, cn, inviteAccessMapFromXML[type(ace)], "NEEDS-ACTION", summary)
+ # Look for existing invite and update its fields or create new one
+ invitation = yield self._invitationForShareeUID(shareeUID)
+ if invitation:
+ yield self._updateInvitation(invitation, access=invitationAccessMapFromXML[type(ace)], summary=summary)
+ else:
+ invitation = yield self._createInvitation(
+ shareeUID=shareeUID,
+ access=invitationAccessMapFromXML[type(ace)],
+ summary=summary)
+ # Send invite notification
+ yield self.sendInviteNotification(invitation, request)
- # Send invite
- yield self.sendInvite(record, request)
-
- # Add to database
- yield self.invitesDB().addOrUpdateRecord(record)
-
- finally:
- lock.clean()
-
returnValue(True)
@@ -597,46 +599,41 @@
def uninviteSingleUserFromShare(self, userid, aces, request):
# Cancel invites - we'll just use whatever userid we are given
- # Acquire a memcache lock based on collection URL and sharee UID
- # TODO: when sharing moves into the store this should be replaced
- # by DB-level locking
- lock = (yield self._createLock(userid, request))
- yield self._acquireLock(lock)
+ sharee = self.principalForCalendarUserAddress(userid)
+ if not sharee:
+ returnValue(False)
- try:
- record = yield self.invitesDB().recordForUserID(userid)
- if record:
- result = (yield self.uninviteRecordFromShare(record, request))
- else:
- result = False
- finally:
- lock.clean()
+ shareeUID = sharee.principalUID()
+ invitation = yield self._invitationForShareeUID(shareeUID)
+ if invitation:
+ result = (yield self.uninviteFromShare(invitation, request))
+ else:
+ result = False
+
returnValue(result)
@inlineCallbacks
- def uninviteRecordFromShare(self, record, request):
+ def uninviteFromShare(self, invitation, request):
# Remove any shared calendar or address book
- sharee = self.principalForCalendarUserAddress(record.userid)
+ sharee = self.principalForUID(invitation.shareeUID())
if sharee:
if self.isCalendarCollection():
- shareeHome = yield sharee.calendarHome(request)
+ shareeHomeResource = yield sharee.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = yield sharee.addressBookHome(request)
- displayname = (yield shareeHome.removeShareByUID(request, record.inviteuid))
-
+ shareeHomeResource = yield sharee.addressBookHome(request)
+ displayName = (yield shareeHomeResource.removeShareByUID(request, invitation.uid()))
# If current user state is accepted then we send an invite with the new state, otherwise
# we cancel any existing invites for the user
- if record and record.state != "ACCEPTED":
- yield self.removeInvite(record, request)
- elif record:
- record.state = "DELETED"
- yield self.sendInvite(record, request, displayname=displayname)
+ if invitation and invitation.state() != "ACCEPTED":
+ yield self.removeInviteNotification(invitation, request)
+ elif invitation:
+ yield self.sendInviteNotification(invitation, request, displayName=displayName, notificationState="DELETED")
- # Remove from database
- yield self.invitesDB().removeRecordForInviteUID(record.inviteuid)
+ # Direct shares for with valid sharee principal will already be deleted
+ yield self._newStoreObject.unshareWith(invitation._shareeHomeChild.viewerHome())
returnValue(True)
@@ -648,7 +645,7 @@
@inlineCallbacks
- def sendInvite(self, record, request, displayname=None):
+ def sendInviteNotification(self, invitation, request, notificationState=None, displayName=None):
ownerPrincipal = (yield self.ownerPrincipal(request))
owner = ownerPrincipal.principalURL()
@@ -656,30 +653,37 @@
hosturl = (yield self.canonicalURL(request))
# Locate notifications collection for user
- sharee = self.principalForCalendarUserAddress(record.userid)
+ sharee = self.principalForUID(invitation.shareeUID())
if sharee is None:
- raise ValueError("sharee is None but userid was valid before")
+ raise ValueError("sharee is None but principalUID was valid before")
# We need to look up the resource so that the response cache notifier is properly initialized
notificationResource = (yield request.locateResource(sharee.notificationURL()))
notifications = notificationResource._newStoreNotifications
+ '''
# Look for existing notification
- oldnotification = (yield notifications.notificationObjectWithUID(record.inviteuid))
+ # oldnotification is not used don't query for it
+ oldnotification = (yield notifications.notificationObjectWithUID(invitation.uid()))
if oldnotification:
# TODO: rollup changes?
pass
+ '''
# Generate invite XML
- typeAttr = {'shared-type': self.sharedResourceType()}
+ userid = "urn:uuid:" + invitation.shareeUID()
+ state = notificationState if notificationState else invitation.state()
+ summary = invitation.summary() if displayName is None else displayName
+
+ typeAttr = {'shared-type':self.sharedResourceType()}
xmltype = customxml.InviteNotification(**typeAttr)
xmldata = customxml.Notification(
customxml.DTStamp.fromString(PyCalendarDateTime.getNowUTC().getText()),
customxml.InviteNotification(
- customxml.UID.fromString(record.inviteuid),
- element.HRef.fromString(record.userid),
- inviteStatusMapToXML[record.state](),
- customxml.InviteAccess(inviteAccessMapToXML[record.access]()),
+ customxml.UID.fromString(invitation.uid()),
+ element.HRef.fromString(userid),
+ invitationStatusMapToXML[state](),
+ customxml.InviteAccess(invitationAccessMapToXML[invitation.access()]()),
customxml.HostURL(
element.HRef.fromString(hosturl),
),
@@ -687,29 +691,28 @@
element.HRef.fromString(owner),
customxml.CommonName.fromString(ownerCN),
),
- customxml.InviteSummary.fromString(record.summary if displayname is None else displayname),
+ customxml.InviteSummary.fromString(summary),
self.getSupportedComponentSet() if self.isCalendarCollection() else None,
**typeAttr
),
).toxml()
# Add to collections
- yield notifications.writeNotificationObject(record.inviteuid, xmltype, xmldata)
+ yield notifications.writeNotificationObject(invitation.uid(), xmltype, xmldata)
-
@inlineCallbacks
- def removeInvite(self, record, request):
+ def removeInviteNotification(self, invitation, request):
# Locate notifications collection for user
- sharee = self.principalForCalendarUserAddress(record.userid)
+ sharee = self.principalForUID(invitation.shareeUID())
if sharee is None:
- raise ValueError("sharee is None but userid was valid before")
- notifications = (yield request.locateResource(sharee.notificationURL()))
+ raise ValueError("sharee is None but principalUID was valid before")
+ notificationResource = (yield request.locateResource(sharee.notificationURL()))
+ notifications = notificationResource._newStoreNotifications
# Add to collections
- yield notifications.deleteNotifictionMessageByUID(request, record.inviteuid)
+ yield notifications.removeNotificationObjectWithUID(invitation.uid())
-
@inlineCallbacks
def _xmlHandleInvite(self, request, docroot):
yield self.authorize(request, (element.Read(), element.Write()))
@@ -912,45 +915,68 @@
("text", "xml") : xmlRequestHandler,
}
-inviteAccessMapToXML = {
+invitationAccessMapToXML = {
"read-only" : customxml.ReadAccess,
"read-write" : customxml.ReadWriteAccess,
}
-inviteAccessMapFromXML = dict([(v, k) for k, v in inviteAccessMapToXML.iteritems()])
+invitationAccessMapFromXML = dict([(v, k) for k, v in invitationAccessMapToXML.iteritems()])
-inviteStatusMapToXML = {
+invitationStatusMapToXML = {
"NEEDS-ACTION" : customxml.InviteStatusNoResponse,
"ACCEPTED" : customxml.InviteStatusAccepted,
"DECLINED" : customxml.InviteStatusDeclined,
"DELETED" : customxml.InviteStatusDeleted,
"INVALID" : customxml.InviteStatusInvalid,
}
-inviteStatusMapFromXML = dict([(v, k) for k, v in inviteStatusMapToXML.iteritems()])
+invitationStatusMapFromXML = dict([(v, k) for k, v in invitationStatusMapToXML.iteritems()])
-class Invite(object):
+invitationStateToBindStatusMap = {
+ "NEEDS-ACTION": _BIND_STATUS_INVITED,
+ "ACCEPTED": _BIND_STATUS_ACCEPTED,
+ "DECLINED": _BIND_STATUS_DECLINED,
+ "INVALID": _BIND_STATUS_INVALID,
+}
+invitationStateFromBindStatusMap = dict((v, k) for k, v in invitationStateToBindStatusMap.iteritems())
+invitationAccessToBindModeMap = {
+ "own": _BIND_MODE_OWN,
+ "read-only": _BIND_MODE_READ,
+ "read-write": _BIND_MODE_WRITE,
+ }
+invitationAccessFromBindModeMap = dict((v, k) for k, v in invitationAccessToBindModeMap.iteritems())
+class Invitation(object):
+ """
+ Invitation is a read-only wrapper for CommonHomeChild, that uses terms similar LegacyInvite sharing.py code base.
+ """
+ def __init__(self, shareeHomeChild):
+ self._shareeHomeChild = shareeHomeChild
+
+ def uid(self):
+ return self._shareeHomeChild.shareUID()
+
+ def shareeUID(self):
+ return self._shareeHomeChild.viewerHome().uid()
+
+ def access(self):
+ return invitationAccessFromBindModeMap.get(self._shareeHomeChild.shareMode())
+
+ def state(self):
+ return invitationStateFromBindStatusMap.get(self._shareeHomeChild.shareStatus())
+
+ def summary(self):
+ return self._shareeHomeChild.shareMessage()
+
+
+class LegacyInvite(object):
+
def __init__(self, inviteuid, userid, principalUID, common_name, access, state, summary):
self.inviteuid = inviteuid
- self.userid = userid
self.principalUID = principalUID
- self.name = common_name
self.access = access
self.state = state
self.summary = summary
- def makePropertyElement(self, includeUID=True):
-
- return customxml.InviteUser(
- customxml.UID.fromString(self.inviteuid) if includeUID else None,
- element.HRef.fromString(self.userid),
- customxml.CommonName.fromString(self.name),
- customxml.InviteAccess(inviteAccessMapToXML[self.access]()),
- inviteStatusMapToXML[self.state](),
- )
-
-
-
class InvitesDatabase(AbstractSQLDatabase, LoggingMixIn):
db_basename = db_prefix + "invites"
@@ -1011,7 +1037,7 @@
self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, PRINCIPALUID, NAME, ACCESS, STATE, SUMMARY)
values (:1, :2, :3, :4, :5, :6, :7)
- """, record.inviteuid, record.userid, record.principalUID, record.name, record.access, record.state, record.summary,
+ """, record.inviteuid, "userid", record.principalUID, "name", record.access, record.state, record.summary,
)
@@ -1097,53 +1123,84 @@
def _makeRecord(self, row):
- return Invite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+ return LegacyInvite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
-
-
class SharedHomeMixin(LinkFollowerMixIn):
"""
A mix-in for calendar/addressbook homes that defines the operations for
manipulating a sharee's set of shared calendars.
"""
+
@inlineCallbacks
- def provisionShare(self, name):
+ def provisionShare(self, child, request=None):
+ share = yield self._shareForHomeChild(child._newStoreObject, request)
+ if share:
+ child.setShare(share)
+
+ @inlineCallbacks
+ def _shareForHomeChild(self, child, request=None):
# Try to find a matching share
- child = None
- shares = yield self.allShares()
- if name in shares:
- from twistedcaldav.sharedcollection import SharedCollectionResource
- child = SharedCollectionResource(self, shares[name])
- self.putChild(name, child)
- returnValue(child)
+ if not child or child.owned():
+ returnValue(None)
+ sharerHomeChild = yield child.ownerHome().childWithID(child._resourceID)
- @inlineCallbacks
- def allShares(self):
- if not hasattr(self, "_allShares"):
- allShareRecords = yield self.sharesDB().allRecords()
- self._allShares = dict([(share.localname, share) for share in
- allShareRecords])
- returnValue(self._allShares)
+ # get the shared object's URL
+ sharer = self.principalForUID(sharerHomeChild.viewerHome().uid())
+ if not request:
+ # FIXEME: Fake up a request that can be used to get the sharer home resource
+ class _FakeRequest(object):pass
+ fakeRequest = _FakeRequest()
+ setattr(fakeRequest, TRANSACTION_KEY, self._newStoreHome._txn)
+ request = fakeRequest
+ if self._newStoreHome._homeType == ECALENDARTYPE:
+ sharerHomeCollection = yield sharer.calendarHome(request)
+ elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
+ sharerHomeCollection = yield sharer.addressBookHome(request)
+
+ url = joinURL(sharerHomeCollection.url(), sharerHomeChild.name())
+ share = Share(shareeHomeChild=child, sharerHomeChild=sharerHomeChild, url=url)
+
+ returnValue(share)
+
@inlineCallbacks
- def allShareNames(self):
- allShares = yield self.allShares()
- returnValue(tuple(allShares.keys()))
+ def _shareForUID(self, shareUID, request):
+ # since child.shareUID() == child.name() for indirect shares
+ child = yield self._newStoreHome.childWithName(shareUID)
+ if child:
+ share = yield self._shareForHomeChild(child, request)
+ if share and share.uid() == shareUID:
+ returnValue(share)
+ # find direct shares
+ children = yield self._newStoreHome.children()
+ for child in children:
+ share = yield self._shareForHomeChild(child, request)
+ if share and share.uid() == shareUID:
+ returnValue(share)
+
+ returnValue(None)
+
@inlineCallbacks
def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
# Check for old share
- oldShare = yield self.sharesDB().recordForShareUID(inviteUID)
+ oldShare = yield self._shareForUID(inviteUID, request)
# Send the invite reply then add the link
yield self._changeShare(request, "ACCEPTED", hostUrl, inviteUID, displayname)
+ if oldShare:
+ share = oldShare
+ else:
+ sharedCollection = yield request.locateResource(hostUrl)
+ shareeHomeChild = yield self._newStoreHome.childWithName(inviteUID)
+ share = Share(shareeHomeChild=shareeHomeChild, sharerHomeChild=sharedCollection._newStoreObject, url=hostUrl)
- response = (yield self._acceptShare(request, oldShare, SHARETYPE_INVITE, hostUrl, inviteUID, displayname))
+ response = yield self._acceptShare(request, not oldShare, share, displayname)
returnValue(response)
@@ -1151,47 +1208,58 @@
def acceptDirectShare(self, request, hostUrl, resourceUID, displayname=None):
# Just add the link
- oldShare = yield self.sharesDB().recordForShareUID(resourceUID)
- response = (yield self._acceptShare(request, oldShare, SHARETYPE_DIRECT, hostUrl, resourceUID, displayname))
+ oldShare = yield self._shareForUID(resourceUID, request)
+ if oldShare:
+ share = oldShare
+ else:
+ sharedCollection = yield request.locateResource(hostUrl)
+ sharedName = yield sharedCollection._newStoreObject.shareWith(shareeHome=self._newStoreHome,
+ mode=_BIND_MODE_DIRECT,
+ status=_BIND_STATUS_ACCEPTED,
+ message=displayname)
+
+ shareeHomeChild = yield self._newStoreHome.childWithName(sharedName)
+ share = Share(shareeHomeChild=shareeHomeChild, sharerHomeChild=sharedCollection._newStoreObject, url=hostUrl)
+
+ response = yield self._acceptShare(request, not oldShare, share, displayname)
returnValue(response)
-
@inlineCallbacks
- def _acceptShare(self, request, oldShare, sharetype, hostUrl, shareUID, displayname=None):
+ def _acceptShare(self, request, isNewShare, share, displayname=None):
- # Add or update in DB
- if oldShare:
- share = oldShare
- else:
- share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname)
- yield self.sharesDB().addOrUpdateRecord(share)
-
# Get shared collection in non-share mode first
- sharedCollection = (yield request.locateResource(hostUrl))
- ownerPrincipal = (yield self.ownerPrincipal(request))
+ sharedCollection = yield request.locateResource(share.url())
# For a direct share we will copy any calendar-color over using the owners view
color = None
- if sharetype == SHARETYPE_DIRECT and not oldShare and sharedCollection.isCalendarCollection():
+ if share.direct() and isNewShare and sharedCollection.isCalendarCollection():
try:
color = (yield sharedCollection.readProperty(customxml.CalendarColor, request))
except HTTPError:
pass
+ sharee = self.principalForUID(share.shareeUID())
+ if sharedCollection.isCalendarCollection():
+ shareeHomeResource = yield sharee.calendarHome(request)
+ elif sharedCollection.isAddressBookCollection():
+ shareeHomeResource = yield sharee.addressBookHome(request)
+ shareeURL = joinURL(shareeHomeResource.url(), share.name())
+ shareeCollection = yield request.locateResource(shareeURL)
+ shareeCollection.setShare(share)
+
# Set per-user displayname or color to whatever was given
- sharedCollection.setVirtualShare(ownerPrincipal, share)
if displayname:
- yield sharedCollection.writeProperty(element.DisplayName.fromString(displayname), request)
+ yield shareeCollection.writeProperty(element.DisplayName.fromString(displayname), request)
if color:
- yield sharedCollection.writeProperty(customxml.CalendarColor.fromString(color), request)
+ yield shareeCollection.writeProperty(customxml.CalendarColor.fromString(color), request)
# Calendars always start out transparent and with empty default alarms
- if not oldShare and sharedCollection.isCalendarCollection():
- yield sharedCollection.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request)
- yield sharedCollection.writeProperty(caldavxml.DefaultAlarmVEventDateTime.fromString(""), request)
- yield sharedCollection.writeProperty(caldavxml.DefaultAlarmVEventDate.fromString(""), request)
- yield sharedCollection.writeProperty(caldavxml.DefaultAlarmVToDoDateTime.fromString(""), request)
- yield sharedCollection.writeProperty(caldavxml.DefaultAlarmVToDoDate.fromString(""), request)
+ if isNewShare and shareeCollection.isCalendarCollection():
+ yield shareeCollection.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request)
+ yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVEventDateTime.fromString(""), request)
+ yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVEventDate.fromString(""), request)
+ yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVToDoDateTime.fromString(""), request)
+ yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVToDoDate.fromString(""), request)
# Notify client of changes
yield self.notifyChanged()
@@ -1200,7 +1268,7 @@
returnValue(XMLResponse(
code=responsecode.OK,
element=customxml.SharedAs(
- element.HRef.fromString(joinURL(self.url(), share.localname))
+ element.HRef.fromString(joinURL(self.url(), share.name()))
)
))
@@ -1211,13 +1279,13 @@
Remove a shared collection named in resourceName
"""
- # Send a decline when an invite share is removed only
- if share.sharetype == SHARETYPE_INVITE:
- result = (yield self.declineShare(request, share.hosturl, share.shareuid))
- returnValue(result)
- else:
+ if share.direct():
yield self.removeDirectShare(request, share)
returnValue(None)
+ else:
+ # Send a decline when an invite share is removed only
+ result = yield self.declineShare(request, share.url(), share.uid())
+ returnValue(result)
@inlineCallbacks
@@ -1227,14 +1295,13 @@
current display name of the shared collection.
"""
- displayname = None
- share = yield self.sharesDB().recordForShareUID(shareUID)
+ share = yield self._shareForUID(shareUID, request)
if share:
- displayname = yield self.removeDirectShare(request, share)
+ displayName = (yield self.removeDirectShare(request, share))
+ returnValue(displayName)
+ else:
+ returnValue(None)
- returnValue(displayname)
-
-
@inlineCallbacks
def removeDirectShare(self, request, share):
"""
@@ -1242,7 +1309,7 @@
current display name of the shared collection.
"""
- shareURL = joinURL(self.url(), share.localname)
+ shareURL = joinURL(self.url(), share.name())
shared = (yield request.locateResource(shareURL))
displayname = shared.displayName()
@@ -1254,10 +1321,11 @@
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(shareURL, False)
- yield self.sharesDB().removeRecordForShareUID(share.shareuid)
- # Notify client of changes
- yield self.notifyChanged()
+ if share.direct():
+ yield share._sharerHomeChild.unshareWith(share._shareeHomeChild.viewerHome())
+ else:
+ yield share._sharerHomeChild.updateShare(share._shareeHomeChild, status=_BIND_STATUS_DECLINED)
returnValue(displayname)
@@ -1281,7 +1349,7 @@
# Change state in sharer invite
ownerPrincipal = (yield self.ownerPrincipal(request))
- owner = ownerPrincipal.principalURL()
+ ownerPrincipalUID = ownerPrincipal.principalUID()
sharedCollection = (yield request.locateResource(hostUrl))
if sharedCollection is None:
# Original shared collection is gone - nothing we can do except ignore it
@@ -1292,7 +1360,7 @@
))
# Change the record
- yield sharedCollection.changeUserInviteState(request, replytoUID, owner, state, displayname)
+ yield sharedCollection.changeUserInviteState(request, replytoUID, ownerPrincipalUID, state, displayname)
yield self.sendReply(request, ownerPrincipal, sharedCollection, state, hostUrl, replytoUID, displayname)
@@ -1302,7 +1370,8 @@
# Locate notifications collection for sharer
sharer = (yield sharedCollection.ownerPrincipal(request))
- notifications = (yield request.locateResource(sharer.notificationURL()))
+ notificationResource = (yield request.locateResource(sharer.notificationURL()))
+ notifications = notificationResource._newStoreNotifications
# Generate invite XML
notificationUID = "%s-reply" % (replytoUID,)
@@ -1324,7 +1393,7 @@
*(
(
element.HRef.fromString(cua),
- inviteStatusMapToXML[state](),
+ invitationStatusMapToXML[state](),
customxml.HostURL(
element.HRef.fromString(hostUrl),
),
@@ -1338,7 +1407,7 @@
).toxml()
# Add to collections
- yield notifications.addNotification(request, notificationUID, xmltype, xmldata)
+ yield notifications.writeNotificationObject(notificationUID, xmltype, xmldata)
def _handleInviteReply(self, request, invitereplydoc):
@@ -1383,8 +1452,39 @@
self.localname = localname
self.summary = summary
+class Share(object):
+ def __init__(self, sharerHomeChild, shareeHomeChild, url):
+ self._shareeHomeChild = shareeHomeChild
+ self._sharerHomeChild = sharerHomeChild
+ self._sharedResourceURL = url
+ @classmethod
+ def directUID(cls, shareeHome, sharerHomeChild):
+ return "Direct-%s-%s" % (shareeHome._resourceID, sharerHomeChild._resourceID,)
+
+ def uid(self):
+ # Move to CommonHomeChild shareUID?
+ if self._shareeHomeChild.shareMode() == _BIND_MODE_DIRECT:
+ return self.directUID(shareeHome=self._shareeHomeChild.viewerHome(), sharerHomeChild=self._sharerHomeChild,)
+ else:
+ return self._shareeHomeChild.shareUID()
+
+ def direct(self):
+ return self._shareeHomeChild.shareMode() == _BIND_MODE_DIRECT
+
+ def url(self):
+ return self._sharedResourceURL
+
+ def name(self):
+ return self._shareeHomeChild.name()
+
+ def summary(self):
+ return self._shareeHomeChild.shareMessage()
+
+ def shareeUID(self):
+ return self._shareeHomeChild.viewerHome().uid()
+
class SharedCollectionsDatabase(AbstractSQLDatabase, LoggingMixIn):
db_basename = db_prefix + "shares"
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/storebridge.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -32,6 +32,7 @@
from txdav.base.propertystore.base import PropertyName
from txdav.caldav.icalendarstore import QuotaExceeded
from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE
from txdav.idav import PropertyChangeNotAllowedError
from twext.web2 import responsecode
@@ -44,7 +45,7 @@
from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
from twext.web2.responsecode import (
FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
- BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE
+ BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
)
from twistedcaldav import customxml, carddavxml, caldavxml
@@ -96,8 +97,7 @@
return PropertyName(namespace, name)
- # FIXME 'uid' here should be verifying something.
- def get(self, qname, uid=None):
+ def get(self, qname):
try:
return self._newPropertyStore[self._convertKey(qname)]
except KeyError:
@@ -107,7 +107,7 @@
))
- def set(self, property, uid=None):
+ def set(self, property):
try:
self._newPropertyStore[self._convertKey(property.qname())] = property
except PropertyChangeNotAllowedError:
@@ -118,7 +118,7 @@
- def delete(self, qname, uid=None):
+ def delete(self, qname):
try:
del self._newPropertyStore[self._convertKey(qname)]
except KeyError:
@@ -127,11 +127,11 @@
pass
- def contains(self, qname, uid=None, cache=True):
+ def contains(self, qname):
return (self._convertKey(qname) in self._newPropertyStore)
- def list(self, uid=None, filterByUID=True, cache=True):
+ def list(self):
return [(pname.namespace, pname.name) for pname in
self._newPropertyStore.keys()]
@@ -266,15 +266,6 @@
return self._newStoreObject.retrieveOldIndex()
- def invitesDB(self):
- """
- Retrieve the new-style invites DB wrapper.
- """
- if not hasattr(self, "_invitesDB"):
- self._invitesDB = self._newStoreObject.retrieveOldInvites()
- return self._invitesDB
-
-
def exists(self):
# FIXME: tests
return self._newStoreObject is not None
@@ -435,11 +426,11 @@
@rtype: something adaptable to L{twext.web2.iweb.IResponse}
"""
- # Check virtual share first
- isVirtual = self.isVirtualShare()
- if isVirtual:
+ # Check sharee collection first
+ isShareeCollection = self.isShareeCollection()
+ if isShareeCollection:
log.debug("Removing shared collection %s" % (self,))
- yield self.removeVirtualShare(request)
+ yield self.removeShareeCollection(request)
returnValue(NO_CONTENT)
log.debug("Deleting collection %s" % (self,))
@@ -1535,26 +1526,23 @@
def sharedDropboxACEs(self):
aces = ()
- records = yield self._newStoreCalendarObject._parentCollection.retrieveOldInvites().allRecords()
- for record in records:
- # Invite shares use access mode from the invite
- if record.state != "ACCEPTED":
- continue
-
+ calendars = yield self._newStoreCalendarObject._parentCollection.asShared()
+ for calendar in calendars:
+
userprivs = [
]
- if record.access in ("read-only", "read-write", "read-write-schedule",):
+ if calendar.shareMode() in (_BIND_MODE_READ, _BIND_MODE_WRITE,):
userprivs.append(davxml.Privilege(davxml.Read()))
userprivs.append(davxml.Privilege(davxml.ReadACL()))
userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
- if record.access in ("read-only",):
+ if calendar.shareMode() in (_BIND_MODE_READ,):
userprivs.append(davxml.Privilege(davxml.WriteProperties()))
- if record.access in ("read-write", "read-write-schedule",):
+ if calendar.shareMode() in (_BIND_MODE_WRITE,):
userprivs.append(davxml.Privilege(davxml.Write()))
proxyprivs = list(userprivs)
proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
- principal = self.principalForUID(record.principalUID)
+ principal = self.principalForUID(calendar._home.uid())
aces += (
# Inheritable specific access for the resource's associated principal.
davxml.ACE(
@@ -1613,14 +1601,21 @@
if content_type is None:
content_type = MimeType("application", "octet-stream")
- creating = (self._newStoreAttachment is None)
- if creating:
- self._newStoreAttachment = self._newStoreObject = (
- yield self._newStoreCalendarObject.createAttachmentWithName(
- self.attachmentName))
- t = self._newStoreAttachment.store(content_type)
- yield readStream(request.stream, t.write)
try:
+ creating = (self._newStoreAttachment is None)
+ if creating:
+ self._newStoreAttachment = self._newStoreObject = (
+ yield self._newStoreCalendarObject.createAttachmentWithName(
+ self.attachmentName))
+ t = self._newStoreAttachment.store(content_type)
+ yield readStream(request.stream, t.write)
+ except Exception, e:
+ log.error("Unable to store attachment: %s" % (e,))
+ # Signal to abort in twistedcaldav.resource.CalDAVResource.RenderHTTP
+ self.transactionError()
+ raise HTTPError(SERVICE_UNAVAILABLE)
+
+ try:
yield t.loseConnection()
except QuotaExceeded:
raise HTTPError(
@@ -2207,13 +2202,6 @@
return self.getChild(segments[0]), segments[1:]
- def notificationsDB(self):
- """
- Retrieve the new-style index wrapper.
- """
- return self._newStoreNotifications.retrieveOldIndex()
-
-
def exists(self):
# FIXME: tests
return True
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,249 +1,301 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+# Calendar Server Localization
+# Copyright (c) 2008-2012 Apple Inc. All rights reserved.
#
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2008-10-24 15:03+PDT\n"
+"POT-Creation-Date: 2012-09-28 10:13-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
-"Language-Team: LANGUAGE <LL at li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-#: localization.py:171
+#: twistedcaldav/localization.py:178
msgid "All day"
msgstr ""
-#: localization.py:177
+#: twistedcaldav/localization.py:205
+#, python-format
msgid "%(startTime)s to %(endTime)s"
msgstr ""
-#: localization.py:191
+#: twistedcaldav/localization.py:220
+#, python-format
msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "AM"
msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "PM"
msgstr ""
-#: localization.py:213
+#: twistedcaldav/localization.py:242
+#, python-format
msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
msgstr ""
-#: localization.py:236
+#: twistedcaldav/localization.py:266
+msgid "1 day"
+msgstr ""
+
+#: twistedcaldav/localization.py:268
+#, python-format
+msgid "%(dayCount)d days"
+msgstr ""
+
+#: twistedcaldav/localization.py:276
+msgid "1 hour"
+msgstr ""
+
+#: twistedcaldav/localization.py:278
+#, python-format
+msgid "%(hourCount)d hours"
+msgstr ""
+
+#: twistedcaldav/localization.py:282
+msgid "1 minute"
+msgstr ""
+
+#: twistedcaldav/localization.py:284
+#, python-format
+msgid "%(minuteCount)d minutes"
+msgstr ""
+
+#: twistedcaldav/localization.py:288
+msgid "1 second"
+msgstr ""
+
+#: twistedcaldav/localization.py:290
+#, python-format
+msgid "%(secondCount)d seconds"
+msgstr ""
+
+#: twistedcaldav/localization.py:302
msgid "Monday"
msgstr ""
-#: localization.py:237
+#: twistedcaldav/localization.py:303
msgid "Tuesday"
msgstr ""
-#: localization.py:238
+#: twistedcaldav/localization.py:304
msgid "Wednesday"
msgstr ""
-#: localization.py:239
+#: twistedcaldav/localization.py:305
msgid "Thursday"
msgstr ""
-#: localization.py:240
+#: twistedcaldav/localization.py:306
msgid "Friday"
msgstr ""
-#: localization.py:241
+#: twistedcaldav/localization.py:307
msgid "Saturday"
msgstr ""
-#: localization.py:242
+#: twistedcaldav/localization.py:308
msgid "Sunday"
msgstr ""
-#: localization.py:246
+#: twistedcaldav/localization.py:312
msgid "Mon"
msgstr ""
-#: localization.py:247
+#: twistedcaldav/localization.py:313
msgid "Tue"
msgstr ""
-#: localization.py:248
+#: twistedcaldav/localization.py:314
msgid "Wed"
msgstr ""
-#: localization.py:249
+#: twistedcaldav/localization.py:315
msgid "Thu"
msgstr ""
-#: localization.py:250
+#: twistedcaldav/localization.py:316
msgid "Fri"
msgstr ""
-#: localization.py:251
+#: twistedcaldav/localization.py:317
msgid "Sun"
msgstr ""
-#: localization.py:252
+#: twistedcaldav/localization.py:318
msgid "Sat"
msgstr ""
-#: localization.py:257
+#: twistedcaldav/localization.py:323
msgid "January"
msgstr ""
-#: localization.py:258
+#: twistedcaldav/localization.py:324
msgid "February"
msgstr ""
-#: localization.py:259
+#: twistedcaldav/localization.py:325
msgid "March"
msgstr ""
-#: localization.py:260
+#: twistedcaldav/localization.py:326
msgid "April"
msgstr ""
-#: localization.py:261 localization.py:277
+#: twistedcaldav/localization.py:327
msgid "May"
msgstr ""
-#: localization.py:262
+#: twistedcaldav/localization.py:328
msgid "June"
msgstr ""
-#: localization.py:263
+#: twistedcaldav/localization.py:329
msgid "July"
msgstr ""
-#: localization.py:264
+#: twistedcaldav/localization.py:330
msgid "August"
msgstr ""
-#: localization.py:265
+#: twistedcaldav/localization.py:331
msgid "September"
msgstr ""
-#: localization.py:266
+#: twistedcaldav/localization.py:332
msgid "October"
msgstr ""
-#: localization.py:267
+#: twistedcaldav/localization.py:333
msgid "November"
msgstr ""
-#: localization.py:268
+#: twistedcaldav/localization.py:334
msgid "December"
msgstr ""
-#: localization.py:273
-msgid "Jan"
+#: twistedcaldav/localization.py:339
+msgid "JAN"
msgstr ""
-#: localization.py:274
-msgid "Feb"
+#: twistedcaldav/localization.py:340
+msgid "FEB"
msgstr ""
-#: localization.py:275
-msgid "Mar"
+#: twistedcaldav/localization.py:341
+msgid "MAR"
msgstr ""
-#: localization.py:276
-msgid "Apr"
+#: twistedcaldav/localization.py:342
+msgid "APR"
msgstr ""
-#: localization.py:278
-msgid "Jun"
+#: twistedcaldav/localization.py:343
+msgid "MAY"
msgstr ""
-#: localization.py:279
-msgid "Jul"
+#: twistedcaldav/localization.py:344
+msgid "JUN"
msgstr ""
-#: localization.py:280
-msgid "Aug"
+#: twistedcaldav/localization.py:345
+msgid "JUL"
msgstr ""
-#: localization.py:281
-msgid "Sep"
+#: twistedcaldav/localization.py:346
+msgid "AUG"
msgstr ""
-#: localization.py:282
-msgid "Oct"
+#: twistedcaldav/localization.py:347
+msgid "SEP"
msgstr ""
-#: localization.py:283
-msgid "Nov"
+#: twistedcaldav/localization.py:348
+msgid "OCT"
msgstr ""
-#: localization.py:284
-msgid "Dec"
+#: twistedcaldav/localization.py:349
+msgid "NOV"
msgstr ""
-#: mail.py:726 mail.py:755 mail.py:792
-msgid "Event cancelled"
+#: twistedcaldav/localization.py:350
+msgid "DEC"
msgstr ""
-#: mail.py:727
+#: twistedcaldav/mail.py:267
+#, python-format
+msgid "Event canceled: %(summary)s"
+msgstr ""
+
+#: twistedcaldav/mail.py:269
+#, python-format
msgid "Event invitation: %(summary)s"
msgstr ""
-#: mail.py:736
-msgid "Event Invitation"
+#: twistedcaldav/mail.py:271
+#, python-format
+msgid "Event update: %(summary)s"
msgstr ""
-#: mail.py:737
-msgid "Date"
+#: twistedcaldav/mail.py:273
+#, python-format
+msgid "Event reply: %(summary)s"
msgstr ""
-#: mail.py:738
-msgid "Time"
+#: twistedcaldav/mail.py:276
+msgid "Event Canceled"
msgstr ""
-#: mail.py:739
-msgid "Description"
+#: twistedcaldav/mail.py:279
+msgid "Event Invitation"
msgstr ""
-#: mail.py:740
-msgid "Organizer"
+#: twistedcaldav/mail.py:281
+msgid "Event Update"
msgstr ""
-#: mail.py:741
-msgid "Attendees"
+#: twistedcaldav/mail.py:283
+msgid "Event Reply"
msgstr ""
-#: mail.py:742
-msgid "Location"
+#: twistedcaldav/mail.py:286
+msgid "Date"
msgstr ""
+#: twistedcaldav/mail.py:287
+msgid "Time"
+msgstr ""
-msgid "1 day"
-msgstr
+#: twistedcaldav/mail.py:288
+msgid "Duration"
+msgstr ""
-msgid "%(dayCount)d days"
-msgstr
+#: twistedcaldav/mail.py:289
+msgid "Occurs"
+msgstr ""
-msgid "1 hour"
+#: twistedcaldav/mail.py:290
+msgid "Description"
+msgstr ""
-msgid "%(hourCount)d hours"
-msgstr
+#: twistedcaldav/mail.py:291
+msgid "URL"
+msgstr ""
-msgid "1 minute"
-msgstr
+#: twistedcaldav/mail.py:292
+msgid "Organizer"
+msgstr ""
-msgid "%(minuteCount)d minutes"
-msgstr
+#: twistedcaldav/mail.py:293
+msgid "Attendees"
+msgstr ""
-msgid "1 second"
-msgstr
+#: twistedcaldav/mail.py:294
+msgid "Location"
+msgstr ""
-msgid "%(secondCount)d seconds"
-msgstr
-
+#: twistedcaldav/mail.py:1656
+msgid "(Repeating)"
+msgstr ""
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po 2012-10-01 12:44:46 UTC (rev 9884)
@@ -1,250 +1,301 @@
-# Pig Latin Translation
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+# Calendar Server Localization
+# Copyright (c) 2008-2012 Apple Inc. All rights reserved.
#
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2008-10-24 15:03+PDT\n"
+"POT-Creation-Date: 2012-09-28 10:13-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
-"Language-Team: LANGUAGE <LL at li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-#: localization.py:171
+#: twistedcaldav/localization.py:178
msgid "All day"
msgstr "Allway ayday"
-#: localization.py:177
+#: twistedcaldav/localization.py:205
+#, python-format
msgid "%(startTime)s to %(endTime)s"
msgstr "%(startTime)s otay %(endTime)s"
-#: localization.py:191
+#: twistedcaldav/localization.py:220
+#, python-format
msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
-msgstr "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
+msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "AM"
-msgstr "AMWAY"
+msgstr ""
-#: localization.py:207
+#: twistedcaldav/localization.py:236
msgid "PM"
-msgstr "PMAY"
+msgstr ""
-#: localization.py:213
+#: twistedcaldav/localization.py:242
+#, python-format
msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
msgstr "%(hour24Number)02d:%(minuteNumber)02d"
-#: localization.py:236
+#: twistedcaldav/localization.py:266
+msgid "1 day"
+msgstr "1 ayday"
+
+#: twistedcaldav/localization.py:268
+#, python-format
+msgid "%(dayCount)d days"
+msgstr "%(dayCount)d aysday"
+
+#: twistedcaldav/localization.py:276
+msgid "1 hour"
+msgstr "1 ourhay"
+
+#: twistedcaldav/localization.py:278
+#, python-format
+msgid "%(hourCount)d hours"
+msgstr "%(hourCount)d ourshay"
+
+#: twistedcaldav/localization.py:282
+msgid "1 minute"
+msgstr ""
+
+#: twistedcaldav/localization.py:284
+#, python-format
+msgid "%(minuteCount)d minutes"
+msgstr "%(minuteCount)d inutesmay"
+
+#: twistedcaldav/localization.py:288
+msgid "1 second"
+msgstr "1 econdsay"
+
+#: twistedcaldav/localization.py:290
+#, python-format
+msgid "%(secondCount)d seconds"
+msgstr "%(secondCount)d econdsay"
+
+#: twistedcaldav/localization.py:302
msgid "Monday"
msgstr "Ondaymay"
-#: localization.py:237
+#: twistedcaldav/localization.py:303
msgid "Tuesday"
msgstr "Uesdaytay"
-#: localization.py:238
+#: twistedcaldav/localization.py:304
msgid "Wednesday"
msgstr "Ednesdayway"
-#: localization.py:239
+#: twistedcaldav/localization.py:305
msgid "Thursday"
msgstr "Ursdaythay"
-#: localization.py:240
+#: twistedcaldav/localization.py:306
msgid "Friday"
msgstr "Idayfray"
-#: localization.py:241
+#: twistedcaldav/localization.py:307
msgid "Saturday"
msgstr "Aturdaysay"
-#: localization.py:242
+#: twistedcaldav/localization.py:308
msgid "Sunday"
msgstr "Undaysay"
-#: localization.py:246
+#: twistedcaldav/localization.py:312
msgid "Mon"
msgstr ""
-#: localization.py:247
+#: twistedcaldav/localization.py:313
msgid "Tue"
msgstr ""
-#: localization.py:248
+#: twistedcaldav/localization.py:314
msgid "Wed"
msgstr ""
-#: localization.py:249
+#: twistedcaldav/localization.py:315
msgid "Thu"
msgstr ""
-#: localization.py:250
+#: twistedcaldav/localization.py:316
msgid "Fri"
msgstr ""
-#: localization.py:251
+#: twistedcaldav/localization.py:317
msgid "Sun"
msgstr ""
-#: localization.py:252
+#: twistedcaldav/localization.py:318
msgid "Sat"
msgstr ""
-#: localization.py:257
+#: twistedcaldav/localization.py:323
msgid "January"
msgstr "Anuaryjay"
-#: localization.py:258
+#: twistedcaldav/localization.py:324
msgid "February"
msgstr "Ebruaryfay"
-#: localization.py:259
+#: twistedcaldav/localization.py:325
msgid "March"
msgstr "Archmay"
-#: localization.py:260
+#: twistedcaldav/localization.py:326
msgid "April"
msgstr "Aprilway"
-#: localization.py:261 localization.py:277
+#: twistedcaldav/localization.py:327
msgid "May"
msgstr "Aymay"
-#: localization.py:262
+#: twistedcaldav/localization.py:328
msgid "June"
msgstr "Unejay"
-#: localization.py:263
+#: twistedcaldav/localization.py:329
msgid "July"
msgstr "Ulyjay"
-#: localization.py:264
+#: twistedcaldav/localization.py:330
msgid "August"
msgstr "Augustway"
-#: localization.py:265
+#: twistedcaldav/localization.py:331
msgid "September"
msgstr "Eptembersay"
-#: localization.py:266
+#: twistedcaldav/localization.py:332
msgid "October"
msgstr "Octoberway"
-#: localization.py:267
+#: twistedcaldav/localization.py:333
msgid "November"
msgstr "Ovembernay"
-#: localization.py:268
+#: twistedcaldav/localization.py:334
msgid "December"
msgstr "Ecemberday"
-#: localization.py:273
+#: twistedcaldav/localization.py:339
msgid "JAN"
msgstr "ANJAY"
-#: localization.py:274
-msgid "Feb"
+#: twistedcaldav/localization.py:340
+msgid "FEB"
msgstr ""
-#: localization.py:275
-msgid "Mar"
+#: twistedcaldav/localization.py:341
+msgid "MAR"
msgstr ""
-#: localization.py:276
-msgid "Apr"
+#: twistedcaldav/localization.py:342
+msgid "APR"
msgstr ""
-#: localization.py:278
-msgid "Jun"
+#: twistedcaldav/localization.py:343
+msgid "MAY"
msgstr ""
-#: localization.py:279
-msgid "Jul"
+#: twistedcaldav/localization.py:344
+msgid "JUN"
msgstr ""
-#: localization.py:280
-msgid "Aug"
+#: twistedcaldav/localization.py:345
+msgid "JUL"
msgstr ""
-#: localization.py:281
-msgid "Sep"
+#: twistedcaldav/localization.py:346
+msgid "AUG"
msgstr ""
-#: localization.py:282
-msgid "Oct"
+#: twistedcaldav/localization.py:347
+msgid "SEP"
msgstr ""
-#: localization.py:283
-msgid "Nov"
+#: twistedcaldav/localization.py:348
+msgid "OCT"
msgstr ""
-#: localization.py:284
-msgid "Dec"
+#: twistedcaldav/localization.py:349
+msgid "NOV"
msgstr ""
-#: mail.py:726 mail.py:755 mail.py:792
-msgid "Event cancelled"
-msgstr "Eventway ancelledcay"
+#: twistedcaldav/localization.py:350
+msgid "DEC"
+msgstr ""
-#: mail.py:727
+#: twistedcaldav/mail.py:267
+#, python-format
+msgid "Event canceled: %(summary)s"
+msgstr ""
+
+#: twistedcaldav/mail.py:269
+#, python-format
msgid "Event invitation: %(summary)s"
-msgstr "Eventway invitationway: %(summary)s"
+msgstr ""
-#: mail.py:736
-msgid "Event Invitation"
-msgstr "Eventway invitationway"
+#: twistedcaldav/mail.py:271
+#, python-format
+msgid "Event update: %(summary)s"
+msgstr ""
-#: mail.py:737
-msgid "Date"
-msgstr "Ateday"
+#: twistedcaldav/mail.py:273
+#, python-format
+msgid "Event reply: %(summary)s"
+msgstr ""
-#: mail.py:738
-msgid "Time"
-msgstr "Imetay"
+#: twistedcaldav/mail.py:276
+msgid "Event Canceled"
+msgstr ""
-#: mail.py:739
-msgid "Description"
-msgstr "Escriptionday"
+#: twistedcaldav/mail.py:279
+msgid "Event Invitation"
+msgstr ""
-#: mail.py:740
-msgid "Organizer"
-msgstr "Organizerway"
+#: twistedcaldav/mail.py:281
+msgid "Event Update"
+msgstr ""
-#: mail.py:741
-msgid "Attendees"
-msgstr "Attendeesway"
+#: twistedcaldav/mail.py:283
+msgid "Event Reply"
+msgstr ""
-#: mail.py:742
-msgid "Location"
-msgstr "Ocationlay"
+#: twistedcaldav/mail.py:286
+msgid "Date"
+msgstr ""
+#: twistedcaldav/mail.py:287
+msgid "Time"
+msgstr ""
-msgid "1 day"
-msgstr "1 ayday"
+#: twistedcaldav/mail.py:288
+msgid "Duration"
+msgstr ""
-msgid "%(dayCount)d days"
-msgstr "%(dayCount)d aysday"
+#: twistedcaldav/mail.py:289
+msgid "Occurs"
+msgstr ""
-msgid "1 hour"
-msgstr "1 ourhay"
+#: twistedcaldav/mail.py:290
+msgid "Description"
+msgstr ""
-msgid "%(hourCount)d hours"
-msgstr "%(hourCount)d ourshay"
+#: twistedcaldav/mail.py:291
+msgid "URL"
+msgstr ""
-msgid "1 minute"
-msgstr "1 inutemay"
+#: twistedcaldav/mail.py:292
+msgid "Organizer"
+msgstr ""
-msgid "%(minuteCount)d minutes"
-msgstr "%(minuteCount)d inutesmay"
+#: twistedcaldav/mail.py:293
+msgid "Attendees"
+msgstr ""
-msgid "1 second"
-msgstr "1 econdsay"
+#: twistedcaldav/mail.py:294
+msgid "Location"
+msgstr ""
-msgid "%(secondCount)d seconds"
-msgstr "%(secondCount)d econdsay"
-
+#: twistedcaldav/mail.py:1656
+msgid "(Repeating)"
+msgstr ""
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_link.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_link.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_link.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -22,7 +22,6 @@
from twistedcaldav.linkresource import LinkResource
from twistedcaldav.resource import CalendarHomeResource
-from twistedcaldav.sharedcollection import SharedCollectionResource
from twistedcaldav.test.util import TestCase
@@ -48,6 +47,9 @@
def __init__(self, link):
self.hosturl = link
+ def url(self):
+ return self.hosturl
+
class LinkResourceTests(TestCase):
@inlineCallbacks
@@ -92,31 +94,3 @@
self.assertEqual(e.response.code, responsecode.LOOP_DETECTED)
else:
self.fail("HTTPError exception not raised")
-
-class SharedCollectionResourceTests(TestCase):
-
- @inlineCallbacks
- def test_okLink(self):
- resource = StubCalendarHomeResource(self.site.resource, "home", object(), StubHome())
- self.site.resource.putChild("home", resource)
- link = SharedCollectionResource(resource, StubShare("/home/outbox/"))
- resource.putChild("link", link)
-
- request = SimpleRequest(self.site, "GET", "/home/link/")
- linked_to, _ignore = (yield resource.locateChild(request, ["link",]))
- self.assertTrue(linked_to is resource.getChild("outbox"))
-
- @inlineCallbacks
- def test_badLink(self):
- resource = CalendarHomeResource(self.site.resource, "home", object(), StubHome())
- self.site.resource.putChild("home", resource)
- link = SharedCollectionResource(resource, StubShare("/home/outbox/abc"))
- resource.putChild("link", link)
-
- request = SimpleRequest(self.site, "GET", "/home/link/")
- try:
- yield resource.locateChild(request, ["link",])
- except HTTPError, e:
- self.assertEqual(e.response.code, responsecode.NOT_FOUND)
- else:
- self.fail("HTTPError exception not raised")
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_localization.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_localization.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_localization.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -44,7 +44,10 @@
('cross-timezone', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VTIMEZONE\r\nTZID:US/Eastern\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:EST\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:EDT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T110500\r\nDTEND;TZID=US/Eastern:20081025T181500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+ ('30-hour-long', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T110500\r\nDTEND;TZID=US/Pacific:20081026T171500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+ ('49-hour-long', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T110500\r\nDTEND;TZID=US/Pacific:20081027T121500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
)
localeDir = os.path.join(os.path.dirname(__file__), "data", "locales")
@@ -64,7 +67,7 @@
def test_TimeFormattingAMPM(self):
- with translationTo('English', localeDir=localeDir) as t:
+ with translationTo('en', localeDir=localeDir) as t:
self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 0, 0, 0)), "12:00 AM")
self.assertEquals(t.dtTime(PyCalendarDateTime(2000, 1, 1, 12, 0, 0)), "12:00 PM")
@@ -84,7 +87,7 @@
def test_CalendarFormatting(self):
- with translationTo('English', localeDir=localeDir) as t:
+ with translationTo('en', localeDir=localeDir) as t:
comp = data[0][1]
self.assertEquals(t.date(comp), "Saturday, October 25, 2008")
@@ -111,6 +114,14 @@
self.assertEquals(t.time(comp),
(u'11:05 AM (PDT) to 6:15 PM (EDT)', u'4 hours 10 minutes'))
+ comp = data[6][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 AM to 5:15 PM (PDT)', u'1 day 6 hours 10 minutes'))
+
+ comp = data[7][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 AM to 12:15 PM (PDT)', u'2 days 1 hour 10 minutes'))
+
self.assertEquals(t.monthAbbreviation(1), "JAN")
with translationTo('pig', localeDir=localeDir) as t:
@@ -140,6 +151,15 @@
self.assertEquals(t.time(comp),
(u'11:05 (PDT) otay 18:15 (EDT)', u'4 ourshay 10 inutesmay'))
+ comp = data[6][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 otay 17:15 (PDT)', u'1 ayday 6 ourshay 10 inutesmay'))
+
+ comp = data[7][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 otay 12:15 (PDT)', u'2 aysday 1 ourhay 10 inutesmay'))
+
+
self.assertEquals(t.monthAbbreviation(1), "ANJAY")
def test_getLanguage(self):
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_resource.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_resource.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -331,8 +331,8 @@
else:
self.assertEqual(str(default.children[0]), "/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk/")
- # Force the new calendar to think it is a virtual share
- newadbk._isVirtualShare = True
+ # Force the new calendar to think it is a sharee collection
+ newadbk._isShareeCollection = True
try:
default = yield home.readProperty(carddavxml.DefaultAddressBookURL, request)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_sharing.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_sharing.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -25,7 +25,7 @@
from twistedcaldav import customxml
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase, norequest
-from twistedcaldav.sharing import SharedCollectionMixin, SHARETYPE_DIRECT, WikiDirectoryService
+from twistedcaldav.sharing import SharedCollectionMixin, WikiDirectoryService
from twistedcaldav.resource import CalDAVResource
from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
@@ -47,11 +47,19 @@
self.calendarUserAddresses = set((cuaddr,))
def __init__(self, cuaddr):
- self.path = "/principals/__uids__/%s" % (cuaddr[7:].split('@')[0],)
- self.homepath = "/calendars/__uids__/%s" % (cuaddr[7:].split('@')[0],)
- self.displayname = cuaddr[7:].split('@')[0].upper()
- self.record = self.FakeRecord(cuaddr[7:].split('@')[0], cuaddr)
+ if cuaddr.startswith("mailto:"):
+ name = cuaddr[7:].split('@')[0]
+ elif cuaddr.startswith("urn:uuid:"):
+ name = cuaddr[9:]
+ else:
+ name = cuaddr
+ self.path = "/principals/__uids__/%s" % (name,)
+ self.homepath = "/calendars/__uids__/%s" % (name,)
+ self.displayname = name.upper()
+ self.record = self.FakeRecord(name, cuaddr)
+
+
def calendarHome(self, request):
class FakeHome(object):
def removeShareByUID(self, request, uid):
@@ -61,24 +69,31 @@
def principalURL(self):
return self.path
+ def principalUID(self):
+ return self.record.guid
+
def displayName(self):
return self.displayname
@inlineCallbacks
def setUp(self):
+ self.calendarStore = yield buildStore(self, StubNotifierFactory())
+
yield super(SharingTests, self).setUp()
self.patch(config.Sharing, "Enabled", True)
self.patch(config.Sharing.Calendars, "Enabled", True)
- CalDAVResource.validUserIDForShare = self._fakeValidUserID
- CalDAVResource.validUserIDWithCommonNameForShare = self._fakeValidUserID_CN
- CalDAVResource.sendInvite = lambda self, record, request: succeed(True)
- CalDAVResource.removeInvite = lambda self, record, request: succeed(True)
+ CalDAVResource.sendInviteNotification = lambda self, record, request: succeed(True)
+ CalDAVResource.removeInviteNotification = lambda self, record, request: succeed(True)
- self.patch(CalDAVResource, "principalForCalendarUserAddress", lambda self, cuaddr: SharingTests.FakePrincipal(cuaddr))
+ self.patch(CalDAVResource, "validUserIDForShare", lambda self, userid, request: None if "bogus" in userid else SharingTests.FakePrincipal(userid).principalURL())
+ self.patch(CalDAVResource, "principalForCalendarUserAddress", lambda self, cuaddr: None if "bogus" in cuaddr else SharingTests.FakePrincipal(cuaddr))
+ self.patch(CalDAVResource, "principalForUID", lambda self, principalUID: SharingTests.FakePrincipal("urn:uuid:" + principalUID))
+ def createDataStore(self):
+ return self.calendarStore
@inlineCallbacks
def _refreshRoot(self, request=None):
@@ -91,36 +106,6 @@
returnValue(result)
- def _fakeValidUserID_Base(self, userid, request, *args):
- if userid.startswith("/principals/"):
- return userid
- if userid.endswith("@example.com"):
- principal = SharingTests.FakePrincipal(userid)
- return principal.path if len(args) == 0 else (userid, principal.path, principal.displayname,)
- else:
- return None if len(args) == 0 else (None, None, None,)
-
-
- def _fakeValidUserID(self, userid, request, *args):
- return succeed(self._fakeValidUserID_Base(userid, request, *args))
-
-
- def _fakeValidUserID_CN(self, userid, *args):
- return self._fakeValidUserID_Base(userid, None, *args)
-
-
- def _fakeInvalidUserID_Base(self, userid, request, *args):
- return None if len(args) == 0 else (None, None, None,)
-
-
- def _fakeInvalidUserID(self, userid, request, *args):
- return succeed(self._fakeInvalidUserID_Base(userid, request, *args))
-
-
- def _fakeInvalidUserID_CN(self, userid, *args):
- return self._fakeInvalidUserID_Base(userid, None, *args)
-
-
@inlineCallbacks
def _doPOST(self, body, resultcode=responsecode.OK):
request = SimpleRequest(self.site, "POST", "/calendar/")
@@ -148,8 +133,8 @@
self.assertEquals(rtype, regularCalendarType)
isShared = (yield self.resource.isShared(None))
self.assertFalse(isShared)
- isVShared = self.resource.isVirtualShare()
- self.assertFalse(isVShared)
+ isShareeCollection = self.resource.isShareeCollection()
+ self.assertFalse(isShareeCollection)
self.resource.upgradeToShare()
@@ -157,8 +142,8 @@
self.assertEquals(rtype, sharedOwnerType)
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
- isVShared = self.resource.isVirtualShare()
- self.assertFalse(isVShared)
+ isShareeCollection = self.resource.isShareeCollection()
+ self.assertFalse(isShareeCollection)
@inlineCallbacks
@@ -170,8 +155,8 @@
self.assertEquals(rtype, sharedOwnerType)
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
- isVShared = self.resource.isVirtualShare()
- self.assertFalse(isVShared)
+ isShareeCollection = self.resource.isShareeCollection()
+ self.assertFalse(isShareeCollection)
yield self.resource.downgradeFromShare(None)
@@ -179,8 +164,8 @@
self.assertEquals(rtype, regularCalendarType)
isShared = (yield self.resource.isShared(None))
self.assertFalse(isShared)
- isVShared = self.resource.isVirtualShare()
- self.assertFalse(isVShared)
+ isShareeCollection = self.resource.isShareeCollection()
+ self.assertFalse(isShareeCollection)
@inlineCallbacks
@@ -202,7 +187,7 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
@@ -211,8 +196,8 @@
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
- isVShared = self.resource.isVirtualShare()
- self.assertFalse(isVShared)
+ isShareeCollection = self.resource.isShareeCollection()
+ self.assertFalse(isShareeCollection)
@inlineCallbacks
@@ -233,7 +218,7 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
@@ -242,8 +227,8 @@
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
- isVShared = (yield self.resource.isVirtualShare())
- self.assertFalse(isVShared)
+ isShareeCollection = (yield self.resource.isShareeCollection())
+ self.assertFalse(isShareeCollection)
@inlineCallbacks
@@ -282,7 +267,7 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadAccess()),
customxml.InviteStatusNoResponse(),
@@ -357,21 +342,21 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
),
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user03 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user03"),
customxml.CommonName.fromString("USER03"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
),
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user04 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user04"),
customxml.CommonName.fromString("USER04"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
@@ -415,21 +400,20 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
),
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user04 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user04"),
customxml.CommonName.fromString("USER04"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
),
))
-
@inlineCallbacks
def test_POSTaddRemoveSameInvitee(self):
@@ -466,14 +450,14 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user02 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user02"),
customxml.CommonName.fromString("USER02"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
),
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user03 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user03"),
customxml.CommonName.fromString("USER03"),
customxml.InviteAccess(customxml.ReadAccess()),
customxml.InviteStatusNoResponse(),
@@ -576,23 +560,23 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user01 at example.com"),
+ davxml.HRef.fromString("urn:uuid:user01"),
customxml.CommonName.fromString("USER01"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusNoResponse(),
)
))
- self.resource.validUserIDForShare = self._fakeInvalidUserID
- self.resource.validUserIDWithCommonNameForShare = self._fakeInvalidUserID_CN
+ self.resource.validUserIDForShare = lambda userid, request: None
self.resource.principalForCalendarUserAddress = lambda cuaddr: None
+ self.resource.principalForUID = lambda principalUID: None
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
customxml.UID.fromString(""),
- davxml.HRef.fromString("mailto:user01 at example.com"),
- customxml.CommonName.fromString("USER01"),
+ davxml.HRef.fromString("urn:uuid:user01"),
+ customxml.CommonName.fromString("user01"),
customxml.InviteAccess(customxml.ReadWriteAccess()),
customxml.InviteStatusInvalid(),
)
@@ -625,18 +609,28 @@
class StubCollection(object):
def __init__(self):
- self._isVirtualShare = True
+ self._isShareeCollection = True
self._shareePrincipal = StubUserPrincipal()
def isCalendarCollection(self):
return True
class StubShare(object):
- def __init__(self):
- self.sharetype = SHARETYPE_DIRECT
- self.hosturl = "/wikifoo"
+ def direct(self):
+ return True
+ def url(self):
+ return "/wikifoo"
+
+ def uid(self):
+ return "012345"
+
+ def shareeUID(self):
+ return StubUserPrincipal().record.guid
+
class TestCollection(SharedCollectionMixin, StubCollection):
- pass
+ def principalForUID(self, uid):
+ principal = StubUserPrincipal()
+ return principal if principal.record.guid == uid else None
class StubRecord(object):
def __init__(self, recordType, name, guid):
@@ -693,7 +687,7 @@
self.assertTrue("<write/>" in acl.toxml())
-
+'''
class DatabaseSharingTests(SharingTests):
@inlineCallbacks
@@ -704,3 +698,5 @@
def createDataStore(self):
return self.calendarStore
+
+'''
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/test/test_wrapping.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -53,6 +53,7 @@
from twistedcaldav.directory.test.test_xmlfile import XMLFileBase
from txdav.caldav.icalendarstore import ICalendarHome
from txdav.carddav.iaddressbookstore import IAddressBookHome
+from txdav.caldav.datastore.file import Calendar
@@ -399,6 +400,8 @@
Exceeding quota on an attachment returns an HTTP error code.
"""
self.patch(config, "EnableDropBox", True)
+ self.patch(Calendar, "asShared", lambda self: [])
+
yield self.populateOneObject("1.ics", test_event_text)
calendarObject = yield self.getResource(
"/calendars/users/wsanchez/dropbox/uid-test.dropbox/too-big-attachment",
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/base.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/base.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -109,16 +109,22 @@
PropertyName.fromElement(TwistedQuotaRootProperty),
))
- def __init__(self, defaultUser):
+ def __init__(self, defaultUser, shareeUser=None):
"""
Instantiate the property store for a user. The default is the default user
(owner) property to read in the case of global or shadowable properties.
+ The sharee user is a user sharing the user to read for per-user properties.
- @param defaultuser: the default user uid
- @type defaultuser: C{str}
+ @param defaultUser: the default user uid
+ @type defaultUser: C{str}
+
+ @param shareeUser: the per user uid or None if the same as defaultUser
+ @type shareeUser: C{str}
"""
- self._perUser = self._defaultUser = defaultUser
+ assert(defaultUser is not None or shareeUser is not None)
+ self._defaultUser = shareeUser if defaultUser is None else defaultUser
+ self._perUser = defaultUser if shareeUser is None else shareeUser
self._shadowableKeys = set(AbstractPropertyStore._defaultShadowableKeys)
self._globalKeys = set(AbstractPropertyStore._defaultGlobalKeys)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -73,13 +73,13 @@
@classmethod
@inlineCallbacks
- def load(cls, defaultuser, txn, resourceID, created=False, notifyCallback=None):
+ def load(cls, defaultuser, shareUser, txn, resourceID, created=False, notifyCallback=None):
"""
@param notifyCallback: a callable used to trigger notifications when the
property store changes.
"""
self = cls.__new__(cls)
- super(PropertyStore, self).__init__(defaultuser)
+ super(PropertyStore, self).__init__(defaultuser, shareUser)
self._txn = txn
self._resourceID = resourceID
self._cached = {}
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/test/test_sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/base/propertystore/test/test_sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -48,10 +48,9 @@
self.addCleanup(self.maybeCommitLast)
self._txn = self.store.newTransaction()
self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
- "user01", self._txn, 1
+ "user01", None, self._txn, 1
)
- self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
- self.propertyStore2._setPerUserUID("user02")
+ self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
@inlineCallbacks
@@ -74,14 +73,13 @@
store = self.propertyStore1
self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
- "user01", self._txn, 1
+ "user01", None, self._txn, 1
)
self.propertyStore1._shadowableKeys = store._shadowableKeys
self.propertyStore1._globalKeys = store._globalKeys
store = self.propertyStore2
- self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
- self.propertyStore2._setPerUserUID("user02")
+ self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
self.propertyStore2._shadowableKeys = store._shadowableKeys
self.propertyStore2._globalKeys = store._globalKeys
@@ -96,14 +94,13 @@
store = self.propertyStore1
self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
- "user01", self._txn, 1
+ "user01", None, self._txn, 1
)
self.propertyStore1._shadowableKeys = store._shadowableKeys
self.propertyStore1._globalKeys = store._globalKeys
store = self.propertyStore2
- self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
- self.propertyStore2._setPerUserUID("user02")
+ self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
self.propertyStore2._shadowableKeys = store._shadowableKeys
self.propertyStore2._globalKeys = store._globalKeys
@@ -127,7 +124,7 @@
pass
self.addCleanup(maybeAbortIt)
concurrentPropertyStore = yield PropertyStore.load(
- "user01", concurrentTxn, 1
+ "user01", None, concurrentTxn, 1
)
concurrentPropertyStore[pname] = pval1
race = []
@@ -155,9 +152,8 @@
def test_copy(self):
# Existing store
- store1_user1 = yield PropertyStore.load("user01", self._txn, 2)
- store1_user2 = yield PropertyStore.load("user01", self._txn, 2)
- store1_user2._setPerUserUID("user02")
+ store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
+ store1_user2 = yield PropertyStore.load("user01", "user02", self._txn, 2)
# Populate current store with data
props_user1 = (
@@ -179,20 +175,18 @@
self._txn = self.store.newTransaction()
# Existing store
- store1_user1 = yield PropertyStore.load("user01", self._txn, 2)
+ store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
# New store
- store2_user1 = yield PropertyStore.load("user01", self._txn, 3)
+ store2_user1 = yield PropertyStore.load("user01", None, self._txn, 3)
# Do copy and check results
yield store2_user1.copyAllProperties(store1_user1)
self.assertEqual(store1_user1.keys(), store2_user1.keys())
- store1_user2 = yield PropertyStore.load("user01", self._txn, 2)
- store1_user2._setPerUserUID("user02")
- store2_user2 = yield PropertyStore.load("user01", self._txn, 3)
- store2_user2._setPerUserUID("user02")
+ store1_user2 = yield PropertyStore.load("user01", "user02", self._txn, 2)
+ store2_user2 = yield PropertyStore.load("user01", "user02", self._txn, 3)
self.assertEqual(store1_user2.keys(), store2_user2.keys())
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/file.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/file.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -148,7 +148,7 @@
results = []
objectResources = (yield self.objectResourcesWithUID(uid, ("inbox",)))
for objectResource in objectResources:
- if allow_shared or objectResource._parentCollection._owned:
+ if allow_shared or objectResource._parentCollection.owned():
results.append(objectResource)
returnValue(results)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -52,9 +52,8 @@
IAttachment
from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
CommonObjectResource, ECALENDARTYPE
-from txdav.common.datastore.sql_legacy import \
- PostgresLegacyIndexEmulator, SQLLegacyCalendarInvites,\
- SQLLegacyCalendarShares, PostgresLegacyInboxIndexEmulator
+from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator,\
+ PostgresLegacyInboxIndexEmulator
from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
_ATTACHMENTS_MODE_NONE, _ATTACHMENTS_MODE_READ, _ATTACHMENTS_MODE_WRITE,\
@@ -115,7 +114,6 @@
self._childClass = Calendar
super(CalendarHome, self).__init__(transaction, ownerUID, notifiers)
- self._shares = SQLLegacyCalendarShares(self)
createCalendarWithName = CommonHome.createChildWithName
@@ -226,7 +224,7 @@
results = []
objectResources = (yield self.objectResourcesWithUID(uid, ["inbox"]))
for objectResource in objectResources:
- if allow_shared or objectResource._parentCollection._owned:
+ if allow_shared or objectResource._parentCollection.owned():
results.append(objectResource)
returnValue(results)
@@ -397,6 +395,7 @@
implements(ICalendar)
# structured tables. (new, preferred)
+ _homeSchema = schema.CALENDAR_HOME
_bindSchema = schema.CALENDAR_BIND
_homeChildSchema = schema.CALENDAR
_homeChildMetaDataSchema = schema.CALENDAR_METADATA
@@ -423,7 +422,6 @@
self._index = PostgresLegacyInboxIndexEmulator(self)
else:
self._index = PostgresLegacyIndexEmulator(self)
- self._invites = SQLLegacyCalendarInvites(self)
@classmethod
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/common.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -995,7 +995,7 @@
yield self.commit()
normalCal = yield self.calendarUnderTest()
otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
- otherCal = yield otherHome.sharedChildWithName(newCalName)
+ otherCal = yield otherHome.childWithName(newCalName)
self.assertNotIdentical(otherCal, None)
self.assertEqual(
(yield
@@ -1003,13 +1003,6 @@
(yield
(yield normalCal.calendarObjectWithName("1.ics")).component())
)
- # Check legacy shares database too, since that's what the protocol layer
- # is still using to list things.
- self.assertEqual(
- [(record.shareuid, record.localname) for record in
- (yield otherHome.retrieveOldShares().allRecords())],
- [(newCalName, newCalName)]
- )
@inlineCallbacks
@@ -1023,17 +1016,15 @@
cal = yield self.calendarUnderTest()
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
newName = yield cal.shareWith(other, _BIND_MODE_READ)
- otherCal = yield other.sharedChildWithName(self.sharedName)
+ otherCal = yield other.childWithName(self.sharedName)
# Name should not change just because we updated the mode.
self.assertEqual(newName, self.sharedName)
self.assertNotIdentical(otherCal, None)
- # FIXME: permission information should be visible on the retrieved
- # calendar object, we shoudln't need to go via the legacy API.
- invites = yield cal.retrieveOldInvites().allRecords()
- self.assertEqual(len(invites), 1)
- self.assertEqual(invites[0].access, "read-only")
+ invitedCals = yield cal.asShared()
+ self.assertEqual(len(invitedCals), 1)
+ self.assertEqual(invitedCals[0].shareMode(), _BIND_MODE_READ)
@inlineCallbacks
@@ -1048,12 +1039,10 @@
cal = yield self.calendarUnderTest()
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
newName = yield cal.unshareWith(other)
- otherCal = yield other.sharedChildWithName(newName)
+ otherCal = yield other.childWithName(newName)
self.assertIdentical(otherCal, None)
- invites = yield cal.retrieveOldInvites().allRecords()
- self.assertEqual(len(invites), 0)
- shares = yield other.retrieveOldShares().allRecords()
- self.assertEqual(len(shares), 0)
+ invitedCals = yield cal.asShared()
+ self.assertEqual(len(invitedCals), 0)
@inlineCallbacks
def test_unshareSharerSide(self, commit=False):
@@ -1066,15 +1055,13 @@
yield self.commit()
cal = yield self.calendarUnderTest()
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
- otherCal = yield other.sharedChildWithName(self.sharedName)
+ otherCal = yield other.childWithName(self.sharedName)
self.assertNotEqual(otherCal, None)
yield cal.unshare()
- otherCal = yield other.sharedChildWithName(self.sharedName)
+ otherCal = yield other.childWithName(self.sharedName)
self.assertEqual(otherCal, None)
- invites = yield cal.retrieveOldInvites().allRecords()
- self.assertEqual(len(invites), 0)
- shares = yield other.retrieveOldShares().allRecords()
- self.assertEqual(len(shares), 0)
+ invitedCals = yield cal.asShared()
+ self.assertEqual(len(invitedCals), 0)
@inlineCallbacks
def test_unshareShareeSide(self, commit=False):
@@ -1087,15 +1074,13 @@
yield self.commit()
cal = yield self.calendarUnderTest()
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
- otherCal = yield other.sharedChildWithName(self.sharedName)
+ otherCal = yield other.childWithName(self.sharedName)
self.assertNotEqual(otherCal, None)
yield otherCal.unshare()
- otherCal = yield other.sharedChildWithName(self.sharedName)
+ otherCal = yield other.childWithName(self.sharedName)
self.assertEqual(otherCal, None)
- invites = yield cal.retrieveOldInvites().allRecords()
- self.assertEqual(len(invites), 0)
- shares = yield other.retrieveOldShares().allRecords()
- self.assertEqual(len(shares), 0)
+ invitedCals = yield cal.asShared()
+ self.assertEqual(len(invitedCals), 0)
@inlineCallbacks
def test_unshareWithInDifferentTransaction(self):
@@ -1145,6 +1130,9 @@
result = (yield home.hasCalendarResourceUIDSomewhereElse("uid2", object, "schedule"))
self.assertTrue(result)
+
+ # FIXME: do this without legacy calls
+ '''
from twistedcaldav.sharing import SharedCollectionRecord
scr = SharedCollectionRecord(
shareuid="opaque", sharetype="D", summary="ignored",
@@ -1157,6 +1145,8 @@
"uid2-5", object, "schedule"
))
self.assertFalse(result)
+ '''
+ yield None
@inlineCallbacks
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/test/test_sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -47,7 +47,6 @@
from twistedcaldav.dateops import datetimeMktime
from twistedcaldav.ical import Component
from twistedcaldav.query import calendarqueryfilter
-from twistedcaldav.sharing import SharedCollectionRecord
import datetime
from pycalendar.datetime import PyCalendarDateTime
@@ -916,38 +915,40 @@
# Provision the home and calendar now
txn = calendarStore.newTransaction()
- home = yield txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
- self.assertNotEqual(home, None)
- cal = yield home.calendarWithName("calendar")
+ sharerHome = yield txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ self.assertNotEqual(sharerHome, None)
+ cal = yield sharerHome.calendarWithName("calendar")
self.assertNotEqual(cal, None)
+ shareeHome = yield txn.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+ self.assertNotEqual(shareeHome, None)
yield txn.commit()
txn1 = calendarStore.newTransaction()
txn2 = calendarStore.newTransaction()
- home1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
- home2 = yield txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ sharerHome1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ self.assertNotEqual(sharerHome1, None)
+ cal1 = yield sharerHome1.calendarWithName("calendar")
+ self.assertNotEqual(cal1, None)
+ shareeHome1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+ self.assertNotEqual(shareeHome1, None)
- shares1 = yield home1.retrieveOldShares()
- shares2 = yield home2.retrieveOldShares()
+ sharerHome2 = yield txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+ self.assertNotEqual(sharerHome2, None)
+ cal2 = yield sharerHome2.calendarWithName("calendar")
+ self.assertNotEqual(cal2, None)
+ shareeHome2 = yield txn1.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+ self.assertNotEqual(shareeHome2, None)
- record = SharedCollectionRecord(
- "abcd",
- "D",
- "/calendars/__uids__/uid2/calendar/",
- "XYZ",
- "Shared Wiki Calendar",
- )
-
@inlineCallbacks
def _defer1():
- yield shares1.addOrUpdateRecord(record)
+ yield cal1.shareWith(shareeHome=sharerHome1, mode=_BIND_MODE_DIRECT, status=_BIND_STATUS_ACCEPTED, message="Shared Wiki Calendar")
yield txn1.commit()
d1 = _defer1()
@inlineCallbacks
def _defer2():
- yield shares2.addOrUpdateRecord(record)
+ yield cal2.shareWith(shareeHome=sharerHome2, mode=_BIND_MODE_DIRECT, status=_BIND_STATUS_ACCEPTED, message="Shared Wiki Calendar")
yield txn2.commit()
d2 = _defer2()
@@ -978,9 +979,9 @@
bind.SEEN_BY_SHAREE: True,
})
yield _bindCreate.on(self.transactionUnderTest())
- sharedCalendar = yield shareeHome.sharedChildWithName("shared_1")
+ sharedCalendar = yield shareeHome.childWithName("shared_1")
self.assertTrue(sharedCalendar is not None)
- sharedCalendar = yield shareeHome.sharedChildWithName("shared_1_vtodo")
+ sharedCalendar = yield shareeHome.childWithName("shared_1_vtodo")
self.assertTrue(sharedCalendar is None)
# Now do the transfer and see if a new binding exists
@@ -988,11 +989,11 @@
"home_splits")).createCalendarWithName("calendar_new")
yield calendar._transferSharingDetails(newcalendar, "VTODO")
- sharedCalendar = yield shareeHome.sharedChildWithName("shared_1")
+ sharedCalendar = yield shareeHome.childWithName("shared_1")
self.assertTrue(sharedCalendar is not None)
self.assertEqual(sharedCalendar._resourceID, calendar._resourceID)
- sharedCalendar = yield shareeHome.sharedChildWithName("shared_1-vtodo")
+ sharedCalendar = yield shareeHome.childWithName("shared_1-vtodo")
self.assertTrue(sharedCalendar is not None)
self.assertEqual(sharedCalendar._resourceID, newcalendar._resourceID)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/util.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/datastore/util.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -405,7 +405,7 @@
"""
component = yield self.component()
calendar = self.calendar()
- isOwner = asAdmin or (calendar._owned and
+ isOwner = asAdmin or (calendar.owned() and
calendar.ownerCalendarHome().uid() == accessUID)
for data_filter in [
PerUserDataFilter(accessUID),
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/caldav/icalendarstore.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -408,7 +408,7 @@
C{txn.calendarHomeWithUID("alice") ...
.calendarWithName("calendar").viewerCalendarHome()} will return Alice's
home, whereas C{txn.calendarHomeWithUID("bob") ...
- .sharedChildWithName("alice's calendar").viewerCalendarHome()} will
+ .childWithName("alice's calendar").viewerCalendarHome()} will
return Bob's calendar home.
@return: (synchronously) the calendar home of the user into which this
@@ -417,12 +417,6 @@
"""
# 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/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/carddav/datastore/sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -38,9 +38,7 @@
from twistedcaldav.memcacher import Memcacher
from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
-from txdav.common.datastore.sql_legacy import \
- PostgresLegacyABIndexEmulator, SQLLegacyAddressBookInvites,\
- SQLLegacyAddressBookShares
+from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
from txdav.carddav.datastore.util import validateAddressBookComponent
from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook,\
@@ -91,7 +89,6 @@
self._childClass = AddressBook
super(AddressBookHome, self).__init__(transaction, ownerUID, notifiers)
- self._shares = SQLLegacyAddressBookShares(self)
addressbooks = CommonHome.children
@@ -148,6 +145,7 @@
implements(IAddressBook)
# structured tables. (new, preferred)
+ _homeSchema = schema.ADDRESSBOOK_HOME
_bindSchema = schema.ADDRESSBOOK_BIND
_homeChildSchema = schema.ADDRESSBOOK
_homeChildMetaDataSchema = schema.ADDRESSBOOK_METADATA
@@ -165,9 +163,8 @@
def __init__(self, *args, **kw):
super(AddressBook, self).__init__(*args, **kw)
self._index = PostgresLegacyABIndexEmulator(self)
- self._invites = SQLLegacyAddressBookInvites(self)
+
-
@property
def _addressbookHome(self):
return self._home
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/file.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -721,6 +721,8 @@
"""
return BIND_OWN
+ def owned(self):
+ return self._owned
_renamedName = None
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -49,14 +49,14 @@
from twext.internet.decorate import memoizedKey
-from txdav.common.datastore.sql_legacy import PostgresLegacyNotificationsEmulator
from txdav.caldav.icalendarstore import ICalendarTransaction, ICalendarStore
from txdav.carddav.iaddressbookstore import IAddressBookTransaction
from txdav.common.datastore.sql_tables import schema
from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
- _BIND_STATUS_ACCEPTED, NOTIFICATION_OBJECT_REVISIONS_TABLE
+ _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, \
+ NOTIFICATION_OBJECT_REVISIONS_TABLE
from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
@@ -82,7 +82,6 @@
from twistedcaldav.customxml import NotificationType
from twistedcaldav.dateops import datetimeMktime, parseSQLTimestamp,\
pyCalendarTodatetime
-from txdav.xml.rfc2518 import DisplayName
from txdav.base.datastore.util import normalizeUUIDOrNot
from twext.enterprise.queue import NullQueuer
@@ -770,7 +769,6 @@
a = ("-- Label: %s\n" % (self._label.replace("%", "%%"),) + a[0],) + a[1:]
if self._store.logSQL:
log.error("SQL: %r %r" % (a, kw,))
- results = ()
try:
results = (yield self._sqlTxn.execSQL(*a, **kw))
finally:
@@ -981,10 +979,8 @@
self._txn = transaction
self._ownerUID = ownerUID
self._resourceID = None
- self._shares = None
self._childrenLoaded = False
self._children = {}
- self._sharedChildren = {}
self._notifiers = notifiers
self._quotaUsedBytes = None
self._created = None
@@ -1022,7 +1018,7 @@
From=home, Where=home.OWNER_UID == Parameter("ownerUID"))
@classproperty
- def _ownerFromFromResourceID(cls): #@NoSelf
+ def _ownerFromResourceID(cls): #@NoSelf
home = cls._homeSchema
return Select([home.OWNER_UID],
From=home,
@@ -1133,7 +1129,7 @@
@classmethod
@inlineCallbacks
def homeUIDWithResourceID(cls, txn, rid):
- rows = (yield cls._ownerFromFromResourceID.on(txn, resourceID=rid))
+ rows = (yield cls._ownerFromResourceID.on(txn, resourceID=rid))
if rows:
returnValue(rows[0][0])
else:
@@ -1157,10 +1153,6 @@
return self._txn
- def retrieveOldShares(self):
- return self._shares
-
-
def name(self):
"""
Implement L{IDataStoreObject.name} to return the uid.
@@ -1185,16 +1177,12 @@
"""
Load and cache all children - Depth:1 optimization
"""
- results1 = (yield self._childClass.loadAllObjects(self, owned=True))
- for result in results1:
+ results = (yield self._childClass.loadAllObjects(self))
+ for result in results:
self._children[result.name()] = result
- results2 = (yield self._childClass.loadAllObjects(self, owned=False))
- for result in results2:
- self._sharedChildren[result.name()] = result
self._childrenLoaded = True
- returnValue(results1 + results2)
+ returnValue(results)
-
def listChildren(self):
"""
Retrieve the names of the children in this home.
@@ -1205,19 +1193,16 @@
if self._childrenLoaded:
return succeed(self._children.keys())
else:
- return self._childClass.listObjects(self, owned=True)
+ return self._childClass.listObjects(self)
- def listSharedChildren(self):
+ def listInvitedChildren(self):
"""
- Retrieve the names of the children in this home.
+ Retrieve the names of the invited children in this home.
@return: an iterable of C{str}s.
"""
- if self._childrenLoaded:
- return succeed(self._sharedChildren.keys())
- else:
- return self._childClass.listObjects(self, owned=False)
+ return self._childClass.listInvitedObjects(self)
@memoizedKey("name", "_children")
@@ -1229,7 +1214,7 @@
@param name: a string.
@return: an L{ICalendar} or C{None} if no such child exists.
"""
- return self._childClass.objectWithName(self, name, owned=True)
+ return self._childClass.objectWithName(self, name)
@memoizedKey("resourceID", "_children")
def childWithID(self, resourceID):
@@ -1242,25 +1227,16 @@
"""
return self._childClass.objectWithID(self, resourceID)
- @memoizedKey("name", "_sharedChildren")
- def sharedChildWithName(self, name):
+ def invitedChildWithName(self, name):
"""
- Retrieve the shared child with the given C{name} contained in this
- home. Return a child object with this home and the name.
+ Retrieve the invited child with the given C{name} contained in this
+ home.
- IMPORTANT: take care when using this. Shared calendars should normally
- be accessed through the owner home collection, not the sharee home collection.
- The only reason for access through sharee home is to do some housekeeping
- for maintaining the revisions database to show shared calendars appearing and
- disappearing in the sharee home.
-
@param name: a string.
- @return: an L{ICalendar} or C{None} if no such child
- exists.
+ @return: an L{ICalendar} or C{None} if no such child exists.
"""
- return self._childClass.objectWithName(self, name, owned=False)
+ return self._childClass.invitedObjectWithName(self, name)
-
@inlineCallbacks
def createChildWithName(self, name):
if name.startswith("."):
@@ -1381,37 +1357,38 @@
# Now deal with shared collections
bind = self._bindSchema
rev = self._revisionsSchema
- shares = yield self.listSharedChildren()
- for sharename in shares:
- sharetoken = 0 if sharename in changed_collections else token
- shareID = (yield Select(
- [bind.RESOURCE_ID], From=bind,
- Where=(bind.RESOURCE_NAME == sharename).And(
- bind.HOME_RESOURCE_ID == self._resourceID).And(
- bind.BIND_MODE != _BIND_MODE_OWN)
- ).on(self._txn))[0][0]
- results = [
- (
- sharename,
- name if name else "",
- wasdeleted
- )
- for name, wasdeleted in
- (yield Select([rev.RESOURCE_NAME, rev.DELETED],
- From=rev,
- Where=(rev.REVISION > sharetoken).And(
- rev.RESOURCE_ID == shareID)).on(self._txn))
- if name
- ]
+ shares = yield self.children()
+ for share in shares:
+ if not share.owned():
+ sharetoken = 0 if share.name() in changed_collections else token
+ shareID = (yield Select(
+ [bind.RESOURCE_ID], From=bind,
+ Where=(bind.RESOURCE_NAME == share.name()).And(
+ bind.HOME_RESOURCE_ID == self._resourceID).And(
+ bind.BIND_MODE != _BIND_MODE_OWN)
+ ).on(self._txn))[0][0]
+ results = [
+ (
+ share.name(),
+ name if name else "",
+ wasdeleted
+ )
+ for name, wasdeleted in
+ (yield Select([rev.RESOURCE_NAME, rev.DELETED],
+ From=rev,
+ Where=(rev.REVISION > sharetoken).And(
+ rev.RESOURCE_ID == shareID)).on(self._txn))
+ if name
+ ]
+
+ for path, name, wasdeleted in results:
+ if wasdeleted:
+ if sharetoken:
+ deleted.append("%s/%s" % (path, name,))
+
+ for path, name, wasdeleted in results:
+ changed.append("%s/%s" % (path, name,))
- for path, name, wasdeleted in results:
- if wasdeleted:
- if sharetoken:
- deleted.append("%s/%s" % (path, name,))
-
- for path, name, wasdeleted in results:
- changed.append("%s/%s" % (path, name,))
-
changed.sort()
deleted.sort()
returnValue((changed, deleted))
@@ -1421,6 +1398,7 @@
def _loadPropertyStore(self):
props = yield PropertyStore.load(
self.uid(),
+ self.uid(),
self._txn,
self._resourceID,
notifyCallback=self.notifyChanged
@@ -1530,7 +1508,7 @@
def _preLockResourceIDQuery(cls): #@NoSelf
meta = cls._homeMetaDataSchema
return Select(From=meta,
- Where=meta.RESOURCE_ID==Parameter("resourceID"),
+ Where=meta.RESOURCE_ID == Parameter("resourceID"),
ForUpdate=True)
@@ -1953,7 +1931,7 @@
else:
self._syncTokenRevision = (
yield self._completelyNewRevisionQuery.on(
- self._txn, homeID=self._home._resourceID,
+ self._txn, homeID=self.ownerHome()._resourceID,
resourceID=self._resourceID, name=name)
)[0][0]
self._maybeNotify()
@@ -1969,10 +1947,6 @@
class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic):
"""
Common ancestor class of AddressBooks and Calendars.
-
- @ivar _owned: Is this calendar or addressbook referencing its sharer (owner)
- home? (i.e. C{True} if L{ownerCalendarHome} will actually return the
- sharer, C{False} or if it will return a sharee.)
"""
compareAttributes = (
@@ -1984,10 +1958,11 @@
_objectResourceClass = None
_bindSchema = None
- _homeChildSchema = None
+ _homeSchema = None
+ _homeChildSchema = None
_homeChildMetaDataSchema = None
- _revisionsSchema = None
- _objectSchema = None
+ _revisionsSchema = None
+ _objectSchema = None
_bindTable = None
_homeChildTable = None
@@ -1997,7 +1972,7 @@
_objectTable = None
- def __init__(self, home, name, resourceID, owned, mode):
+ def __init__(self, home, name, resourceID, mode, status, message=None, ownerHome=None):
if home._notifiers:
childID = "%s/%s" % (home.uid(), name)
@@ -2009,8 +1984,10 @@
self._home = home
self._name = name
self._resourceID = resourceID
- self._owned = owned
self._bindMode = mode
+ self._bindStatus = status
+ self._bindMessage = message
+ self._ownerHome = home if ownerHome is None else ownerHome
self._created = None
self._modified = None
self._objects = {}
@@ -2018,16 +1995,15 @@
self._syncTokenRevision = None
self._notifiers = notifiers
self._index = None # Derived classes need to set this
- self._invites = None # Derived classes need to set this
@classproperty
- def _ownedChildListQuery(cls): #@NoSelf
+ def _childNamesForHomeID(cls): #@NoSelf
bind = cls._bindSchema
return Select([bind.RESOURCE_NAME], From=bind,
Where=(bind.HOME_RESOURCE_ID ==
- Parameter("resourceID")).And(
- bind.BIND_MODE == _BIND_MODE_OWN))
+ Parameter("homeID")).And
+ (bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
@classmethod
@@ -2060,46 +2036,54 @@
"_modified",
)
- @classproperty
- def _sharedChildListQuery(cls): #@NoSelf
- bind = cls._bindSchema
- return Select([bind.RESOURCE_NAME], From=bind,
- Where=(bind.HOME_RESOURCE_ID ==
- Parameter("resourceID")).And(
- bind.BIND_MODE != _BIND_MODE_OWN).And(
- bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
-
@classmethod
@inlineCallbacks
- def listObjects(cls, home, owned):
+ def listObjects(cls, home):
"""
Retrieve the names of the children that exist in the given home.
@return: an iterable of C{str}s.
"""
# FIXME: tests don't cover this as directly as they should.
- if owned:
- rows = yield cls._ownedChildListQuery.on(
- home._txn, resourceID=home._resourceID)
- else:
- rows = yield cls._sharedChildListQuery.on(
- home._txn, resourceID=home._resourceID)
+ rows = yield cls._childNamesForHomeID.on(
+ home._txn, homeID=home._resourceID)
names = [row[0] for row in rows]
returnValue(names)
+ @classproperty
+ def _invitedBindForHomeID(cls): #@NoSelf
+ bind = cls._bindSchema
+ return cls._bindFor((bind.HOME_RESOURCE_ID == Parameter("homeID"))
+ .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED))
+
@classmethod
- def _allHomeChildrenQuery(cls, owned):
+ @inlineCallbacks
+ def listInvitedObjects(cls, home):
+ """
+ Retrieve the names of the children with invitations in the given home.
+
+ @return: an iterable of C{str}s.
+ """
+ rows = yield cls._invitedBindForHomeID.on(
+ home._txn, homeID=home._resourceID
+ )
+ names = [row[3] for row in rows]
+ returnValue(names)
+
+
+ @classproperty
+ def _childrenAndMetadataForHomeID(cls): #@NoSelf
bind = cls._bindSchema
child = cls._homeChildSchema
childMetaData = cls._homeChildMetaDataSchema
- if owned:
- ownedPiece = bind.BIND_MODE == _BIND_MODE_OWN
- else:
- ownedPiece = (bind.BIND_MODE != _BIND_MODE_OWN).And(
- bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
- columns = [child.RESOURCE_ID, bind.RESOURCE_NAME, bind.BIND_MODE]
+ columns = [bind.BIND_MODE,
+ bind.HOME_RESOURCE_ID,
+ bind.RESOURCE_ID,
+ bind.RESOURCE_NAME,
+ bind.BIND_STATUS,
+ bind.MESSAGE]
columns.extend(cls.metadataColumns())
return Select(columns,
From=child.join(
@@ -2107,48 +2091,30 @@
'left outer').join(
childMetaData, childMetaData.RESOURCE_ID == bind.RESOURCE_ID,
'left outer'),
- Where=(bind.HOME_RESOURCE_ID == Parameter("resourceID")
- ).And(ownedPiece))
+ Where=(bind.HOME_RESOURCE_ID == Parameter("homeID")
+ ).And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
- @classproperty
- def _ownedHomeChildrenQuery(cls): #@NoSelf
- return cls._allHomeChildrenQuery(True)
-
-
- @classproperty
- def _sharedHomeChildrenQuery(cls): #@NoSelf
- return cls._allHomeChildrenQuery(False)
-
-
- @classproperty
- def _insertInviteQuery(cls): #@NoSelf
- inv = schema.INVITE
- return Insert(
- {
- inv.INVITE_UID: Parameter("uid"),
- inv.NAME: Parameter("name"),
- inv.HOME_RESOURCE_ID: Parameter("homeID"),
- inv.RESOURCE_ID: Parameter("resourceID"),
- inv.RECIPIENT_ADDRESS: Parameter("recipient")
- }
- )
-
-
- @classproperty
- def _updateBindQuery(cls): #@NoSelf
+ @classmethod
+ def _updateBindColumnsQuery(cls, columnMap): #@NoSelf
bind = cls._bindSchema
- return Update({bind.BIND_MODE: Parameter("mode"),
- bind.BIND_STATUS: Parameter("status"),
- bind.MESSAGE: Parameter("message")},
+ return Update(columnMap,
Where=
(bind.RESOURCE_ID == Parameter("resourceID"))
.And(bind.HOME_RESOURCE_ID == Parameter("homeID")),
Return=bind.RESOURCE_NAME)
+ @classproperty
+ def _updateBindQuery(cls): #@NoSelf
+ bind = cls._bindSchema
+ return cls._updateBindColumnsQuery(
+ {bind.BIND_MODE: Parameter("mode"),
+ bind.BIND_STATUS: Parameter("status"),
+ bind.MESSAGE: Parameter("message")})
+
@inlineCallbacks
- def shareWith(self, shareeHome, mode):
+ def shareWith(self, shareeHome, mode, status=None, message=None):
"""
Share this (owned) L{CommonHomeChild} with another home.
@@ -2156,16 +2122,25 @@
@type shareeHome: L{CommonHome}
@param mode: The sharing mode; L{_BIND_MODE_READ} or
- L{_BIND_MODE_WRITE}.
+ L{_BIND_MODE_WRITE} or L{_BIND_MODE_DIRECT}
@type mode: L{str}
+ @param status: The sharing mode; L{_BIND_STATUS_INVITED} or
+ L{_BIND_STATUS_ACCEPTED} or L{_BIND_STATUS_DECLINED} or
+ L{_BIND_STATUS_INVALID}.
+ @type mode: L{str}
+
+ @param message: The proposed message to go along with the share, which
+ will be used as the default display name.
+ @type mode: L{str}
+
@return: the name of the shared calendar in the new calendar home.
@rtype: L{str}
"""
- dn = PropertyName.fromElement(DisplayName)
- dnprop = (self.properties().get(dn) or
- DisplayName.fromString(self.name()))
- # FIXME: honor current home type
+
+ if status is None:
+ status = _BIND_STATUS_ACCEPTED
+
@inlineCallbacks
def doInsert(subt):
newName = str(uuid4())
@@ -2173,13 +2148,8 @@
subt, homeID=shareeHome._resourceID,
resourceID=self._resourceID, name=newName, mode=mode,
seenByOwner=True, seenBySharee=True,
- bindStatus=_BIND_STATUS_ACCEPTED,
+ bindStatus=status, message=message
)
- yield self._insertInviteQuery.on(
- subt, uid=newName, name=str(dnprop),
- homeID=shareeHome._resourceID, resourceID=self._resourceID,
- recipient=shareeHome.uid()
- )
returnValue(newName)
try:
sharedName = yield self._txn.subtransaction(doInsert)
@@ -2187,19 +2157,92 @@
# FIXME: catch more specific exception
sharedName = (yield self._updateBindQuery.on(
self._txn,
- mode=mode, status=_BIND_STATUS_ACCEPTED, message=None,
+ mode=mode, status=status, message=message,
resourceID=self._resourceID, homeID=shareeHome._resourceID
))[0][0]
- # Invite already exists; no need to update it, since the name will
- # remain the same.
+
+ # Must send notification to ensure cache invalidation occurs
+ yield self.notifyChanged()
- shareeProps = yield PropertyStore.load(shareeHome.uid(), self._txn,
- self._resourceID)
- shareeProps[dn] = dnprop
returnValue(sharedName)
@inlineCallbacks
+ def updateShare(self, shareeView, mode=None, status=None, message=None, name=None):
+ """
+ Update share mode, status, and message for a home child shared with
+ this (owned) L{CommonHomeChild}.
+
+ @param shareeView: The sharee home child that shares this.
+ @type shareeView: L{CommonHomeChild}
+
+ @param mode: The sharing mode; L{_BIND_MODE_READ} or
+ L{_BIND_MODE_WRITE} or None to not update
+ @type mode: L{str}
+
+ @param status: The sharing mode; L{_BIND_STATUS_INVITED} or
+ L{_BIND_STATUS_ACCEPTED} or L{_BIND_STATUS_DECLINED} or
+ L{_BIND_STATUS_INVALID} or None to not update
+ @type status: L{str}
+
+ @param message: The proposed message to go along with the share, which
+ will be used as the default display name, or None to not update
+ @type message: L{str}
+
+ @param name: The bind resource name or None to not update
+ @type message: L{str}
+
+ @return: the name of the shared item in the sharee's home.
+ @rtype: a L{Deferred} which fires with a L{str}
+ """
+ # TODO: raise a nice exception if shareeView is not, in fact, a shared
+ # version of this same L{CommonHomeChild}
+
+ #remove None parameters, and substitute None for empty string
+ bind = self._bindSchema
+ columnMap = dict([(k, v if v else None)
+ for k,v in {bind.BIND_MODE:mode,
+ bind.BIND_STATUS:status,
+ bind.MESSAGE:message,
+ bind.RESOURCE_NAME:name}.iteritems() if v is not None])
+
+ if len(columnMap):
+
+ #TODO: with bit of parameter wrangling, call shareWith() here instead.
+ sharedname = yield self._updateBindColumnsQuery(columnMap).on(
+ self._txn,
+ resourceID=self._resourceID, homeID=shareeView._home._resourceID
+ )
+
+ #update affected attributes
+ if mode:
+ shareeView._bindMode = columnMap[bind.BIND_MODE]
+
+ if status:
+ shareeView._bindStatus = columnMap[bind.BIND_STATUS]
+ if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
+ yield shareeView._initSyncToken()
+ elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
+ shareeView._deletedSyncToken(sharedRemoval=True);
+
+ if message:
+ shareeView._bindMessage = columnMap[bind.MESSAGE]
+
+ queryCacher = self._txn._queryCacher
+ if queryCacher:
+ cacheKey = queryCacher.keyForObjectWithName(shareeView._home._resourceID, shareeView._name)
+ queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
+ shareeView._name = sharedname[0][0]
+
+ # Must send notification to ensure cache invalidation occurs
+ yield self.notifyChanged()
+
+ returnValue(shareeView._name)
+
+
+
+ @inlineCallbacks
def unshareWith(self, shareeHome):
"""
Remove the shared version of this (owned) L{CommonHomeChild} from the
@@ -2212,15 +2255,38 @@
@return: a L{Deferred} which will fire with the previously-used name.
"""
+
+
+ #remove sync tokens
+ shareeChildren = yield shareeHome.children()
+ for shareeChild in shareeChildren:
+ if not shareeChild.owned() and shareeChild._resourceID == self._resourceID:
+ shareeChild._deletedSyncToken(sharedRemoval=True);
+
+ queryCacher = self._txn._queryCacher
+ if queryCacher:
+ cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, shareeChild._name)
+ queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
+ break;
+
bind = self._bindSchema
- resourceName = (yield Delete(
+ rows = yield Delete(
From=bind,
Where=(bind.RESOURCE_ID == Parameter("resourceID"))
.And(bind.HOME_RESOURCE_ID == Parameter("homeID")),
Return=bind.RESOURCE_NAME,
).on(self._txn, resourceID=self._resourceID,
- homeID=shareeHome._resourceID))[0][0]
- shareeHome._sharedChildren.pop(resourceName, None)
+ homeID=shareeHome._resourceID)
+
+ resourceName = None
+ if rows:
+ resourceName = rows[0][0]
+ shareeHome._children.pop(resourceName, None)
+
+ # Must send notification to ensure cache invalidation occurs
+ yield self.notifyChanged()
+
returnValue(resourceName)
@@ -2230,7 +2296,30 @@
"""
return self._bindMode
+ def owned(self):
+ """
+ @see: L{ICalendar.owned}
+ """
+ return self._bindMode == _BIND_MODE_OWN
+ def shareStatus(self):
+ """
+ @see: L{ICalendar.shareStatus}
+ """
+ return self._bindStatus
+
+ def shareMessage(self):
+ """
+ @see: L{ICalendar.shareMessage}
+ """
+ return self._bindMessage
+
+ def shareUID(self):
+ """
+ @see: L{ICalendar.shareUID}
+ """
+ return self.name()
+
@inlineCallbacks
def unshare(self, homeType):
"""
@@ -2238,8 +2327,7 @@
@param homeType: a valid store type (ECALENDARTYPE or EADDRESSBOOKTYPE)
"""
- mode = self.shareMode()
- if mode == _BIND_MODE_OWN:
+ if self.owned():
# This collection may be shared to others
for sharedToHome in [x.viewerHome() for x in (yield self.asShared())]:
(yield self.unshareWith(sharedToHome))
@@ -2252,16 +2340,28 @@
(yield sharerCollection.unshareWith(self._home))
+ @classmethod
+ def _bindFor(cls, condition): #@NoSelf
+ bind = cls._bindSchema
+ return Select(
+ [bind.BIND_MODE,
+ bind.HOME_RESOURCE_ID,
+ bind.RESOURCE_ID,
+ bind.RESOURCE_NAME,
+ bind.BIND_STATUS,
+ bind.MESSAGE],
+ From=bind,
+ Where=condition
+ )
@classproperty
- def _bindEntriesFor(cls): #@NoSelf
+ def _sharedBindForResourceID(cls): #@NoSelf
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))
+ return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+ .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+ .And(bind.BIND_MODE != _BIND_MODE_OWN)
+ )
+
@inlineCallbacks
@@ -2276,26 +2376,76 @@
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)
+ if not self.owned():
+ returnValue([])
+
+ # get all accepted binds
+ acceptedRows = yield self._sharedBindForResourceID.on(
+ self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
+ )
+
cls = self.__class__ # for ease of grepping...
result = []
- for mode, homeResourceID, sharedResourceName in rows:
+ for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in acceptedRows: #@UnusedVariable
+ assert bindStatus == _BIND_STATUS_ACCEPTED
# 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
+ home=(yield self._txn.homeWithResourceID(self._home._homeType, homeID)),
+ name=resourceName, resourceID=self._resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=self._home
)
yield new.initFromStore()
result.append(new)
returnValue(result)
+ @classproperty
+ def _invitedBindForResourceID(cls): #@NoSelf
+ bind = cls._bindSchema
+ return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+ .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
+ )
+
+ @inlineCallbacks
+ def asInvited(self):
+ """
+ Retrieve all the versions of this L{CommonHomeChild} as it is invited to
+ everyone.
+
+ @see: L{ICalendarHome.asInvited}
+
+ @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.
+ """
+ if not self.owned():
+ returnValue([])
+
+ rows = yield self._invitedBindForResourceID.on(
+ self._txn, resourceID=self._resourceID, homeID=self._home._resourceID,
+ )
+ cls = self.__class__ # for ease of grepping...
+
+ result = []
+ for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in rows: #@UnusedVariable
+ # TODO: this could all be issued in parallel; no need to serialize
+ # the loop.
+ new = cls(
+ home=(yield self._txn.homeWithResourceID(self._home._homeType, homeID)),
+ name=resourceName, resourceID=self._resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=self._home
+ )
+ yield new.initFromStore()
+ result.append(new)
+ returnValue(result)
+
+
@classmethod
@inlineCallbacks
- def loadAllObjects(cls, home, owned):
+ def loadAllObjects(cls, 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
@@ -2306,13 +2456,10 @@
results = []
# Load from the main table first
- if owned:
- query = cls._ownedHomeChildrenQuery
- else:
- query = cls._sharedHomeChildrenQuery
- dataRows = (yield query.on(home._txn, resourceID=home._resourceID))
+ dataRows = (yield cls._childrenAndMetadataForHomeID.on(home._txn, homeID=home._resourceID))
if dataRows:
+
# Get property stores for all these child resources (if any found)
propertyStores = (yield PropertyStore.forMultipleResources(
home.uid(), home._txn,
@@ -2322,15 +2469,10 @@
bind = cls._bindSchema
rev = cls._revisionsSchema
- if owned:
- ownedCond = bind.BIND_MODE == _BIND_MODE_OWN
- else:
- ownedCond = bind.BIND_MODE != _BIND_MODE_OWN
revisions = (yield Select(
[rev.RESOURCE_ID, Max(rev.REVISION)],
From=rev.join(bind, rev.RESOURCE_ID == bind.RESOURCE_ID, 'left'),
Where=(bind.HOME_RESOURCE_ID == home._resourceID).
- And(ownedCond).
And((rev.RESOURCE_NAME != None).Or(rev.DELETED == False)),
GroupBy=rev.RESOURCE_ID
).on(home._txn))
@@ -2338,9 +2480,23 @@
# Create the actual objects merging in properties
for items in dataRows:
- resourceID, resourceName, bindMode = items[:3]
- metadata = items[3:]
- child = cls(home, resourceName, resourceID, owned, bindMode)
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = items[:6] #@UnusedVariable
+ metadata=items[7:]
+
+ if bindStatus == _BIND_MODE_OWN:
+ ownerHome = home
+ else:
+ #TODO: get all ownerHomeIDs at once
+ ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
+ home._txn, resourceID=resourceID))[0][0]
+ ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
+
+ child = cls(
+ home=home,
+ name=resourceName, resourceID=resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=ownerHome
+ )
for attr, value in zip(cls.metadataAttributes(), metadata):
setattr(child, attr, value)
child._syncTokenRevision = revisions[resourceID]
@@ -2350,48 +2506,65 @@
returnValue(results)
+ @classproperty
+ def _invitedBindForNameAndHomeID(cls): #@NoSelf
+ bind = cls._bindSchema
+ return cls._bindFor((bind.RESOURCE_NAME == Parameter("name"))
+ .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+ .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
+ )
+
+
@classmethod
- def _homeChildLookup(cls, ownedPart):
+ @inlineCallbacks
+ def invitedObjectWithName(cls, home, name):
"""
- Common portions of C{_ownedResourceIDByName}
- C{_resourceIDSharedToHomeByName}, except for the 'owned' fragment of the
- Where clause, supplied as an argument.
- """
- bind = cls._bindSchema
- return Select(
- [bind.RESOURCE_ID, bind.BIND_MODE],
- From=bind,
- Where=(bind.RESOURCE_NAME == Parameter('objectName')).And(
- bind.HOME_RESOURCE_ID == Parameter('homeID')).And(
- ownedPart))
+ Retrieve the child with the given C{name} contained in the given
+ C{home}.
+ @param home: a L{CommonHome}.
- @classproperty
- def _resourceIDOwnedByHomeByName(cls): #@NoSelf
+ @param name: a string; the name of the L{CommonHomeChild} to retrieve.
+
+ @param owned: a boolean - whether or not to get a shared child
+ @return: an L{CommonHomeChild} or C{None} if no such child
+ exists.
"""
- DAL query to look up an object resource ID owned by a home, given a
- resource name (C{objectName}), and a home resource ID
- (C{homeID}).
- """
- return cls._homeChildLookup(
- cls._bindSchema.BIND_MODE == _BIND_MODE_OWN)
+ rows = yield cls._invitedBindForNameAndHomeID.on(home._txn,
+ name=name, homeID=home._resourceID)
+ if not rows:
+ returnValue(None)
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+
+ #TODO: combine with _invitedBindForNameAndHomeID and sort results
+ ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
+ home._txn, resourceID=resourceID))[0][0]
+ ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
+
+ child = cls(
+ home=home,
+ name=resourceName, resourceID=resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=ownerHome,
+ )
+ yield child.initFromStore()
+ returnValue(child)
+
+
@classproperty
- def _resourceIDSharedToHomeByName(cls): #@NoSelf
- """
- DAL query to look up an object resource ID shared to a home, given a
- resource name (C{objectName}), and a home resource ID
- (C{homeID}).
- """
- return cls._homeChildLookup(
- (cls._bindSchema.BIND_MODE != _BIND_MODE_OWN).And(
- cls._bindSchema.BIND_STATUS == _BIND_STATUS_ACCEPTED))
+ def _childForNameAndHomeID(cls): #@NoSelf
+ bind = cls._bindSchema
+ return cls._bindFor((bind.RESOURCE_NAME == Parameter("name"))
+ .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+ .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+ )
-
@classmethod
@inlineCallbacks
- def objectWithName(cls, home, name, owned):
+ def objectWithName(cls, home, name):
+ # replaces objectWithName()
"""
Retrieve the child with the given C{name} contained in the given
C{home}.
@@ -2400,53 +2573,67 @@
@param name: a string; the name of the L{CommonHomeChild} to retrieve.
- @param owned: a boolean - whether or not to get a shared child
@return: an L{CommonHomeChild} or C{None} if no such child
exists.
"""
- data = None
+ rows = None
queryCacher = home._txn._queryCacher
- # Only caching non-shared objects so that we don't need to invalidate
- # in sql_legacy
- if owned and queryCacher:
+
+ if queryCacher:
# Retrieve data from cache
cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
- data = yield queryCacher.get(cacheKey)
-
- if data is None:
+ rows = yield queryCacher.get(cacheKey)
+
+ if rows is None:
# No cached copy
- if owned:
- query = cls._resourceIDOwnedByHomeByName
- else:
- query = cls._resourceIDSharedToHomeByName
- data = yield query.on(home._txn,
- objectName=name, homeID=home._resourceID)
- if owned and data and queryCacher:
+ rows = yield cls._childForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
+
+ if rows:
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+ # get ownerHomeID
+ if bindMode == _BIND_MODE_OWN:
+ ownerHomeID = homeID
+ else:
+ ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
+ home._txn, resourceID=resourceID))[0][0]
+ rows[0].append(ownerHomeID)
+
+ if rows and queryCacher:
# Cache the result
- queryCacher.setAfterCommit(home._txn, cacheKey, data)
-
- if not data:
+ queryCacher.setAfterCommit(home._txn, cacheKey, rows)
+
+ if not rows:
returnValue(None)
-
- resourceID, mode = data[0]
- child = cls(home, name, resourceID, owned, mode)
+
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage, ownerHomeID = rows[0] #@UnusedVariable
+
+ if bindMode == _BIND_MODE_OWN:
+ ownerHome = home
+ else:
+ ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
+
+ child = cls(
+ home=home,
+ name=name, resourceID=resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=ownerHome,
+ )
yield child.initFromStore()
returnValue(child)
@classproperty
- def _homeChildByIDQuery(cls): #@NoSelf
+ def _bindForResourceIDAndHomeID(cls): #@NoSelf
"""
DAL query that looks up home child names / bind modes by home child
- resouce ID and home resource ID.
+ resource ID and home resource ID.
"""
bind = cls._bindSchema
- return Select([bind.RESOURCE_NAME, bind.BIND_MODE],
- From=bind,
- Where=(bind.RESOURCE_ID == Parameter("resourceID")
- ).And(bind.HOME_RESOURCE_ID == Parameter("homeID")))
-
-
+ return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+ .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+ )
+
+
@classmethod
@inlineCallbacks
def objectWithID(cls, home, resourceID):
@@ -2459,12 +2646,25 @@
@return: an L{CommonHomeChild} or C{None} if no such child
exists.
"""
- data = yield cls._homeChildByIDQuery.on(
+ rows = yield cls._bindForResourceIDAndHomeID.on(
home._txn, resourceID=resourceID, homeID=home._resourceID)
- if not data:
+ if not rows:
returnValue(None)
- name, mode = data[0]
- child = cls(home, name, resourceID, mode == _BIND_MODE_OWN, mode)
+
+ bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+
+ if bindMode == _BIND_MODE_OWN:
+ ownerHome = home
+ else:
+ ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
+ home._txn, resourceID=resourceID))[0][0]
+ ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
+ child = cls(
+ home=home,
+ name=resourceName, resourceID=resourceID,
+ mode=bindMode, status=bindStatus,
+ message=bindMessage, ownerHome=ownerHome,
+ )
yield child.initFromStore()
returnValue(child)
@@ -2493,7 +2693,7 @@
def _bindInsertQuery(cls, **kw): #@NoSelf
"""
DAL statement to create a bind entry that connects a collection to its
- owner's home.
+ home.
"""
bind = cls._bindSchema
return Insert({
@@ -2502,6 +2702,7 @@
bind.RESOURCE_NAME: Parameter("name"),
bind.BIND_MODE: Parameter("mode"),
bind.BIND_STATUS: Parameter("bindStatus"),
+ bind.MESSAGE: Parameter("message"),
bind.SEEN_BY_OWNER: Parameter("seenByOwner"),
bind.SEEN_BY_SHAREE: Parameter("seenBySharee"),
})
@@ -2510,9 +2711,12 @@
@classmethod
@inlineCallbacks
def create(cls, home, name):
- child = (yield cls.objectWithName(home, name, owned=True))
+ child = (yield cls.objectWithName(home, name))
if child is not None:
raise HomeChildNameAlreadyExistsError(name)
+ invite = (yield cls.invitedObjectWithName(home, name))
+ if invite is not None:
+ raise HomeChildNameAlreadyExistsError(name)
if name.startswith("."):
raise HomeChildNameNotAllowedError(name)
@@ -2530,11 +2734,12 @@
yield cls._bindInsertQuery.on(
home._txn, homeID=home._resourceID, resourceID=resourceID,
name=name, mode=_BIND_MODE_OWN, seenByOwner=True,
- seenBySharee=True, bindStatus=_BIND_STATUS_ACCEPTED
+ seenBySharee=True, bindStatus=_BIND_STATUS_ACCEPTED,
+ message=None,
)
# Initialize other state
- child = cls(home, name, resourceID, True, _BIND_MODE_OWN)
+ child = cls(home, name, resourceID, _BIND_MODE_OWN, _BIND_STATUS_ACCEPTED)
child._created = _created
child._modified = _modified
yield child._loadPropertyStore()
@@ -2601,10 +2806,6 @@
return self._index
- def retrieveOldInvites(self):
- return self._invites
-
-
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
@@ -2699,9 +2900,10 @@
def ownerHome(self):
"""
- (Don't use this method. See interface documentation as to why.)
+ @see: L{ICalendar.ownerCalendarHome}
+ @see: L{IAddressbook.ownerAddessbookHome}
"""
- return self._home
+ return self._ownerHome
def viewerHome(self):
@@ -2713,7 +2915,7 @@
@classproperty
- def _ownerHomeFromResourceQuery(cls): #@NoSelf
+ def _ownerHomeWithResourceID(cls): #@NoSelf
"""
DAL query to retrieve the home resource ID of the owner from the bound
home-child ID.
@@ -2734,20 +2936,16 @@
@return: a L{Deferred} that fires with the resource ID.
@rtype: L{Deferred} firing L{int}
"""
- if self._owned:
+ if self.owned():
# If this was loaded by its owner then we can skip the query, since
# we already know who the owner is.
returnValue(self._home._resourceID)
else:
- rid = (yield self._ownerHomeFromResourceQuery.on(
+ rid = (yield self._ownerHomeWithResourceID.on(
self._txn, resourceID=self._resourceID))[0][0]
returnValue(rid)
- def setSharingUID(self, uid):
- self.properties()._setPerUserUID(uid)
-
-
@inlineCallbacks
def objectResources(self):
"""
@@ -3034,6 +3232,7 @@
if props is None:
props = yield PropertyStore.load(
self.ownerHome().uid(),
+ self.viewerHome().uid(),
self._txn,
self._resourceID,
notifyCallback=self.notifyChanged
@@ -3218,7 +3417,7 @@
if dataRows:
# Get property stores for all these child resources (if any found)
if parent.objectResourcesHaveProperties():
- propertyStores =(yield PropertyStore.forMultipleResources(
+ propertyStores = (yield PropertyStore.forMultipleResources(
parent._home.uid(),
parent._txn,
cls._objectSchema.RESOURCE_ID,
@@ -3286,7 +3485,7 @@
if dataRows:
# Get property stores for all these child resources
if parent.objectResourcesHaveProperties():
- propertyStores =(yield PropertyStore.forMultipleResourcesWithResourceIDs(
+ propertyStores = (yield PropertyStore.forMultipleResourcesWithResourceIDs(
parent._home.uid(),
parent._txn,
tuple([row[0] for row in dataRows]),
@@ -3434,6 +3633,7 @@
if props is None:
if self._parentCollection.objectResourcesHaveProperties():
props = yield PropertyStore.load(
+ self._parentCollection.viewerHome().uid(),
self._parentCollection.ownerHome().uid(),
self._txn,
self._resourceID,
@@ -3699,6 +3899,7 @@
def _loadPropertyStore(self):
self._propertyStore = yield PropertyStore.load(
self._uid,
+ self._uid,
self._txn,
self._resourceID,
notifyCallback=self.notifyChanged
@@ -3709,10 +3910,6 @@
return ResourceType.notification #@UndefinedVariable
- def retrieveOldIndex(self):
- return PostgresLegacyNotificationsEmulator(self)
-
-
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
@@ -3723,8 +3920,20 @@
def uid(self):
return self._uid
+
+ def owned(self):
+ return True
+
+ def ownerHome(self):
+ return self._home
+
+
+ def viewerHome(self):
+ return self._home
+
+
@inlineCallbacks
def notificationObjects(self):
results = (yield NotificationObject.loadAllObjects(self))
@@ -3981,7 +4190,7 @@
if dataRows:
# Get property stores for all these child resources (if any found)
- propertyStores =(yield PropertyStore.forMultipleResources(
+ propertyStores = (yield PropertyStore.forMultipleResources(
parent.uid(),
parent._txn,
schema.NOTIFICATION.RESOURCE_ID,
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql_legacy.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/datastore/sql_legacy.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -29,25 +29,17 @@
from twistedcaldav.config import config
from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
from twistedcaldav.memcachepool import CachePoolUserMixIn
-from twistedcaldav.notifications import NotificationRecord
from twistedcaldav.query import \
calendarqueryfilter, calendarquery, addressbookquery, expression, \
addressbookqueryfilter
from twistedcaldav.query.sqlgenerator import sqlgenerator
-from twistedcaldav.sharing import Invite
-from twistedcaldav.sharing import SharedCollectionRecord
from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
from txdav.common.icommondatastore import IndexedSearchException, \
ReservationError, NoSuchObjectResourceError
-from txdav.common.datastore.sql_tables import (
- _BIND_MODE_OWN, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_DIRECT,
- _BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED,
- _BIND_STATUS_INVALID, CALENDAR_BIND_TABLE, CALENDAR_HOME_TABLE,
- ADDRESSBOOK_HOME_TABLE, ADDRESSBOOK_BIND_TABLE, schema)
-from twext.enterprise.dal.syntax import Delete, Insert, Parameter, \
- SavepointAction, Select, Update
+from txdav.common.datastore.sql_tables import schema
+from twext.enterprise.dal.syntax import Parameter, Select
from twext.python.clsprop import classproperty
from twext.python.log import Logger, LoggingMixIn
@@ -64,685 +56,6 @@
4: 'T',
}
-class PostgresLegacyNotificationsEmulator(object):
- def __init__(self, notificationsCollection):
- self._collection = notificationsCollection
-
-
- @inlineCallbacks
- def _recordForObject(self, notificationObject):
- if notificationObject:
- returnValue(
- NotificationRecord(
- notificationObject.uid(),
- notificationObject.name(),
- (yield notificationObject.xmlType().toxml())
- )
- )
- else:
- returnValue(None)
-
-
- def recordForName(self, name):
- return self._recordForObject(
- self._collection.notificationObjectWithName(name)
- )
-
-
- @inlineCallbacks
- def recordForUID(self, uid):
- returnValue((yield self._recordForObject(
- (yield self._collection.notificationObjectWithUID(uid))
- )))
-
-
- def removeRecordForUID(self, uid):
- return self._collection.removeNotificationObjectWithUID(uid)
-
-
- def removeRecordForName(self, name):
- return self._collection.removeNotificationObjectWithName(name)
-
-
-
-class SQLLegacyInvites(object):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
- _homeTable = None
- _bindTable = None
-
- _homeSchema = None
- _bindSchema = None
-
- def __init__(self, collection):
- self._collection = collection
-
- # Since we do multi-table requests we need a dict that combines tables
- self._combinedTable = {}
- for key, value in self._homeTable.iteritems():
- self._combinedTable["HOME:%s" % (key,)] = value
- for key, value in self._bindTable.iteritems():
- self._combinedTable["BIND:%s" % (key,)] = value
-
-
- @property
- def _txn(self):
- return self._collection._txn
-
-
- def _getHomeWithUID(self, uid):
- raise NotImplementedError()
-
-
- 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."
-
-
- @classmethod
- def _allColumnsQuery(cls, condition):
- inv = schema.INVITE
- home = cls._homeSchema
- bind = cls._bindSchema
- return Select(
- [inv.INVITE_UID,
- inv.NAME,
- inv.RECIPIENT_ADDRESS,
- home.OWNER_UID,
- bind.BIND_MODE,
- bind.BIND_STATUS,
- bind.MESSAGE],
- From=inv.join(home).join(bind),
- Where=(
- condition
- .And(inv.RESOURCE_ID == bind.RESOURCE_ID)
- .And(inv.HOME_RESOURCE_ID == home.RESOURCE_ID)
- .And(inv.HOME_RESOURCE_ID == bind.HOME_RESOURCE_ID)),
- OrderBy=inv.NAME, Ascending=True
- )
-
-
- @classproperty
- def _allRecordsQuery(cls): #@NoSelf
- """
- DAL query for all invite records with a given resource ID.
- """
- inv = schema.INVITE
- return cls._allColumnsQuery(inv.RESOURCE_ID == Parameter("resourceID"))
-
-
- @inlineCallbacks
- def allRecords(self):
- values = []
- rows = yield self._allRecordsQuery.on(
- self._txn, resourceID=self._collection._resourceID
- )
- for row in rows:
- values.append(self._makeInvite(row))
- returnValue(values)
-
-
- @classproperty
- def _inviteForRecipientQuery(cls): #@NoSelf
- """
- DAL query to retrieve an invite record for a given recipient address.
- """
- inv = schema.INVITE
- return cls._allColumnsQuery(
- (inv.RESOURCE_ID == Parameter("resourceID")).And(inv.RECIPIENT_ADDRESS == Parameter("recipient"))
- )
-
-
- @inlineCallbacks
- def recordForUserID(self, userid):
- rows = yield self._inviteForRecipientQuery.on(
- self._txn,
- resourceID=self._collection._resourceID,
- recipient=userid
- )
- returnValue(self._makeInvite(rows[0]) if rows else None)
-
-
- @classproperty
- def _inviteForPrincipalUIDQuery(cls): #@NoSelf
- """
- DAL query to retrieve an invite record for a given principal UID.
- """
- inv = schema.INVITE
- home = cls._homeSchema
- return cls._allColumnsQuery(
- (inv.RESOURCE_ID == Parameter("resourceID")).And(home.OWNER_UID == Parameter("principalUID"))
- )
-
-
- @inlineCallbacks
- def recordForPrincipalUID(self, principalUID):
- rows = yield self._inviteForPrincipalUIDQuery.on(
- self._txn,
- resourceID=self._collection._resourceID,
- principalUID=principalUID
- )
- returnValue(self._makeInvite(rows[0]) if rows else None)
-
-
- @classproperty
- def _inviteForUIDQuery(cls): #@NoSelf
- """
- DAL query to retrieve an invite record for a given recipient address.
- """
- inv = schema.INVITE
- return cls._allColumnsQuery(inv.INVITE_UID == Parameter("uid"))
-
-
- @inlineCallbacks
- def recordForInviteUID(self, inviteUID):
- rows = yield self._inviteForUIDQuery.on(self._txn, uid=inviteUID)
- returnValue(self._makeInvite(rows[0]) if rows else None)
-
-
- def _makeInvite(self, row):
- [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_OWN: "own",
- _BIND_MODE_READ: "read-only",
- _BIND_MODE_WRITE: "read-write"
- }[bindMode]
- return Invite(
- inviteuid, userid, ownerUID, common_name,
- access, state, summary
- )
-
-
- @classproperty
- def _updateBindQuery(cls): #@NoSelf
- bind = cls._bindSchema
-
- return Update({bind.BIND_MODE: Parameter("mode"),
- bind.BIND_STATUS: Parameter("status"),
- bind.MESSAGE: Parameter("message")},
- Where=
- (bind.RESOURCE_ID == Parameter("resourceID"))
- .And(bind.HOME_RESOURCE_ID == Parameter("homeID")))
-
-
- @classproperty
- def _idsForInviteUID(cls): #@NoSelf
- inv = schema.INVITE
- return Select([inv.RESOURCE_ID, inv.HOME_RESOURCE_ID],
- From=inv,
- Where=inv.INVITE_UID == Parameter("inviteuid"))
-
-
- @classproperty
- def _updateInviteQuery(cls): #@NoSelf
- """
- DAL query to update an invitation for a given recipient.
- """
- inv = schema.INVITE
- return Update({inv.NAME: Parameter("name")},
- Where=inv.INVITE_UID == Parameter("uid"))
-
-
- @classproperty
- def _insertBindQuery(cls): #@NoSelf
- bind = cls._bindSchema
- return Insert(
- {
- bind.HOME_RESOURCE_ID: Parameter("homeID"),
- bind.RESOURCE_ID: Parameter("resourceID"),
- bind.BIND_MODE: Parameter("mode"),
- bind.BIND_STATUS: Parameter("status"),
- bind.MESSAGE: Parameter("message"),
- bind.RESOURCE_NAME: Parameter("resourceName"),
- bind.SEEN_BY_OWNER: False,
- bind.SEEN_BY_SHAREE: False,
- }
- )
-
-
- @classproperty
- def _insertInviteQuery(cls): #@NoSelf
- inv = schema.INVITE
- return Insert(
- {
- inv.INVITE_UID: Parameter("uid"),
- inv.NAME: Parameter("name"),
- inv.HOME_RESOURCE_ID: Parameter("homeID"),
- inv.RESOURCE_ID: Parameter("resourceID"),
- inv.RECIPIENT_ADDRESS: Parameter("recipient")
- }
- )
-
-
- @inlineCallbacks
- 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]
- shareeHome = yield self._getHomeWithUID(record.principalUID)
- rows = yield self._idsForInviteUID.on(self._txn,
- inviteuid=record.inviteuid)
-
- # FIXME: Do the BIND table query before the INVITE table query because BIND currently has proper
- # constraints in place, whereas INVITE does not. Really we need to do this in a sub-transaction so
- # we can roll back if any one query fails.
- if rows:
- [[resourceID, homeResourceID]] = rows
- yield self._updateBindQuery.on(
- self._txn,
- mode=bindMode, status=bindStatus, message=record.summary,
- resourceID=resourceID, homeID=homeResourceID
- )
- yield self._updateInviteQuery.on(
- self._txn, name=record.name, uid=record.inviteuid
- )
- else:
- yield self._insertBindQuery.on(
- self._txn,
- homeID=shareeHome._resourceID,
- resourceID=self._collection._resourceID,
- resourceName=record.inviteuid,
- mode=bindMode,
- status=bindStatus,
- message=record.summary
- )
- yield self._insertInviteQuery.on(
- self._txn, uid=record.inviteuid, name=record.name,
- homeID=shareeHome._resourceID,
- resourceID=self._collection._resourceID,
- recipient=record.userid
- )
-
- # Must send notification to ensure cache invalidation occurs
- self._collection.notifyChanged()
-
-
- @classmethod
- def _deleteOneBindQuery(cls, constraint):
- inv = schema.INVITE
- bind = cls._bindSchema
- return Delete(
- From=bind, Where=(bind.HOME_RESOURCE_ID, bind.RESOURCE_ID) ==
- Select([inv.HOME_RESOURCE_ID, inv.RESOURCE_ID],
- From=inv, Where=constraint))
-
-
- @classmethod
- def _deleteOneInviteQuery(cls, constraint):
- inv = schema.INVITE
- return Delete(From=inv, Where=constraint)
-
-
- @classproperty
- def _deleteBindByUID(cls): #@NoSelf
- inv = schema.INVITE
- return cls._deleteOneBindQuery(inv.INVITE_UID == Parameter("uid"))
-
-
- @classproperty
- def _deleteInviteByUID(cls): #@NoSelf
- inv = schema.INVITE
- return cls._deleteOneInviteQuery(inv.INVITE_UID == Parameter("uid"))
-
-
- @inlineCallbacks
- def removeRecordForInviteUID(self, inviteUID):
- yield self._deleteBindByUID.on(self._txn, uid=inviteUID)
- yield self._deleteInviteByUID.on(self._txn, uid=inviteUID)
-
- # Must send notification to ensure cache invalidation occurs
- self._collection.notifyChanged()
-
-
-
-class SQLLegacyCalendarInvites(SQLLegacyInvites):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
- _homeTable = CALENDAR_HOME_TABLE
- _bindTable = CALENDAR_BIND_TABLE
-
- _homeSchema = schema.CALENDAR_HOME
- _bindSchema = schema.CALENDAR_BIND
-
- def _getHomeWithUID(self, uid):
- return self._txn.calendarHomeWithUID(uid, create=True)
-
-
-
-class SQLLegacyAddressBookInvites(SQLLegacyInvites):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
- _homeTable = ADDRESSBOOK_HOME_TABLE
- _bindTable = ADDRESSBOOK_BIND_TABLE
-
- _homeSchema = schema.ADDRESSBOOK_HOME
- _bindSchema = schema.ADDRESSBOOK_BIND
-
- def _getHomeWithUID(self, uid):
- return self._txn.addressbookHomeWithUID(uid, create=True)
-
-
-
-class SQLLegacyShares(object):
-
- _homeTable = None
- _bindTable = None
- _urlTopSegment = None
-
- _homeSchema = None
- _bindSchema = None
-
- def __init__(self, home):
- self._home = home
-
-
- @property
- def _txn(self):
- return self._home._txn
-
-
- def _getHomeWithUID(self, uid):
- raise NotImplementedError()
-
-
- def create(self):
- pass
-
-
- def remove(self):
- pass
-
-
- @classproperty
- def _allSharedToQuery(cls): #@NoSelf
- bind = cls._bindSchema
- return Select(
- [bind.RESOURCE_ID, bind.RESOURCE_NAME,
- bind.BIND_MODE, bind.MESSAGE],
- From=bind,
- Where=(bind.HOME_RESOURCE_ID == Parameter("homeID"))
- .And(bind.BIND_MODE != _BIND_MODE_OWN)
- .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
- )
-
-
- @classproperty
- def _inviteUIDByResourceIDsQuery(cls): #@NoSelf
- inv = schema.INVITE
- return Select(
- [inv.INVITE_UID], From=inv, Where=
- (inv.RESOURCE_ID == Parameter("resourceID"))
- .And(inv.HOME_RESOURCE_ID == Parameter("homeID"))
- )
-
-
- @classproperty
- def _ownerHomeIDAndName(cls): #@NoSelf
- bind = cls._bindSchema
- return Select(
- [bind.HOME_RESOURCE_ID, bind.RESOURCE_NAME], From=bind, Where=
- (bind.RESOURCE_ID == Parameter("resourceID"))
- .And(bind.BIND_MODE == _BIND_MODE_OWN)
- )
-
-
- @classproperty
- def _ownerUIDFromHomeID(cls): #@NoSelf
- home = cls._homeSchema
- return Select(
- [home.OWNER_UID], From=home,
- Where=home.RESOURCE_ID == Parameter("homeID")
- )
-
-
-
-
- @inlineCallbacks
- 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 binds in a particular homes) is
- # much simpler anyway; we should just do that.
- all = []
- shareRows = yield self._allSharedToQuery.on(
- self._txn, homeID=self._home._resourceID)
- for resourceID, resourceName, bindMode, summary in shareRows:
- [[ownerHomeID, ownerResourceName]] = yield (
- self._ownerHomeIDAndName.on(self._txn,
- resourceID=resourceID))
- [[ownerUID]] = yield self._ownerUIDFromHomeID.on(
- self._txn, homeID=ownerHomeID)
- hosturl = '/%s/__uids__/%s/%s' % (
- self._urlTopSegment, ownerUID, ownerResourceName
- )
- localname = resourceName
- if bindMode != _BIND_MODE_DIRECT:
- sharetype = 'I'
- [[shareuid]] = yield self._inviteUIDByResourceIDsQuery.on(
- self._txn, resourceID=resourceID,
- homeID=self._home._resourceID
- )
- else:
- sharetype = 'D'
- shareuid = "Direct-%s-%s" % (self._home._resourceID, resourceID,)
- record = SharedCollectionRecord(
- shareuid, sharetype, hosturl, localname, summary
- )
- all.append(record)
- returnValue(all)
-
- def directShareID(self, shareeHome, sharerCollection):
- return "Direct-%s-%s" % (shareeHome._newStoreHome._resourceID, sharerCollection._newStoreObject._resourceID,)
-
- @inlineCallbacks
- def _search(self, **kw):
- [[key, value]] = kw.items()
- for record in (yield self.allRecords()):
- if getattr(record, key) == value:
- returnValue((record))
-
-
- def recordForShareUID(self, shareUID):
- return self._search(shareuid=shareUID)
-
-
- @classproperty
- def _updateBindName(cls): #@NoSelf
- bind = cls._bindSchema
- return Update({bind.RESOURCE_NAME: Parameter("localname")},
- Where=(bind.HOME_RESOURCE_ID == Parameter("homeID"))
- .And(bind.RESOURCE_ID == Parameter('resourceID')))
-
-
- @classproperty
- def _acceptDirectShareQuery(cls): #@NoSelf
- bind = cls._bindSchema
- return Insert({
- bind.HOME_RESOURCE_ID: Parameter("homeID"),
- bind.RESOURCE_ID: Parameter("resourceID"),
- bind.RESOURCE_NAME: Parameter("name"),
- bind.MESSAGE: Parameter("message"),
- bind.BIND_MODE: _BIND_MODE_DIRECT,
- bind.BIND_STATUS: _BIND_STATUS_ACCEPTED,
- bind.SEEN_BY_OWNER: True,
- bind.SEEN_BY_SHAREE: True,
- })
-
-
- @inlineCallbacks
- def addOrUpdateRecord(self, record):
- # record.hosturl -> /.../__uids__/<uid>/<name>
- splithost = record.hosturl.split('/')
-
- # Double-check the path
- if splithost[2] != "__uids__":
- raise ValueError(
- "Sharing URL must be a __uids__ path: %s" % (record.hosturl,))
-
- ownerUID = splithost[3]
- ownerCollectionName = splithost[4]
- ownerHome = yield self._getHomeWithUID(ownerUID)
- ownerCollection = yield ownerHome.childWithName(ownerCollectionName)
- collectionResourceID = ownerCollection._resourceID
-
- if record.sharetype == 'I':
- # 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.
- # XXX_BIND.XXX_RESOURCE_NAME.
-
- yield self._updateBindName.on(
- self._txn, localname=record.localname,
- homeID=self._home._resourceID, resourceID=collectionResourceID
- )
- elif record.sharetype == 'D':
- # There is no bind entry already so add one - but be aware of possible race to create
-
- # Use savepoint so we can do a partial rollback if there is a race condition
- # where this row has already been inserted
- savepoint = SavepointAction("addOrUpdateRecord")
- yield savepoint.acquire(self._txn)
-
- try:
- yield self._acceptDirectShareQuery.on(
- self._txn, homeID=self._home._resourceID,
- resourceID=collectionResourceID, name=record.localname,
- message=record.summary
- )
- except Exception: # FIXME: Really want to trap the pg.DatabaseError but in a non-DB specific manner
- yield savepoint.rollback(self._txn)
-
- # For now we will assume that the insert already done is the winner - so nothing more to do here
- else:
- yield savepoint.release(self._txn)
-
- shareeCollection = yield self._home.sharedChildWithName(record.localname)
- yield shareeCollection._initSyncToken()
-
-
- @classproperty
- def _unbindShareQuery(cls): #@NoSelf
- bind = cls._bindSchema
- return Update({
- bind.BIND_STATUS: _BIND_STATUS_DECLINED
- }, Where=(bind.RESOURCE_NAME == Parameter("name"))
- .And(bind.HOME_RESOURCE_ID == Parameter("homeID")))
-
-
- @inlineCallbacks
- def removeRecordForLocalName(self, localname):
- record = yield self.recordForLocalName(localname)
- shareeCollection = yield self._home.sharedChildWithName(record.localname)
- yield shareeCollection._deletedSyncToken(sharedRemoval=True)
-
- result = yield self._unbindShareQuery.on(self._txn, name=localname,
- homeID=self._home._resourceID)
- returnValue(result)
-
-
- @classproperty
- def _removeInviteShareQuery(cls): #@NoSelf
- """
- DAL query to remove a non-direct share by invite UID.
- """
- bind = cls._bindSchema
- inv = schema.INVITE
- return Update(
- {bind.BIND_STATUS: _BIND_STATUS_DECLINED},
- Where=(bind.HOME_RESOURCE_ID, bind.RESOURCE_ID) ==
- Select([inv.HOME_RESOURCE_ID, inv.RESOURCE_ID],
- From=inv, Where=inv.INVITE_UID == Parameter("uid")))
-
-
- @classproperty
- def _removeDirectShareQuery(cls): #@NoSelf
- """
- DAL query to remove a direct share by its homeID and resourceID.
- """
- bind = cls._bindSchema
- return Delete(From=bind,
- Where=(bind.HOME_RESOURCE_ID == Parameter("homeID"))
- .And(bind.RESOURCE_ID == Parameter("resourceID")))
-
-
- @inlineCallbacks
- def removeRecordForShareUID(self, shareUID):
-
- record = yield self.recordForShareUID(shareUID)
- shareeCollection = yield self._home.sharedChildWithName(record.localname)
- yield shareeCollection._deletedSyncToken(sharedRemoval=True)
-
- if not shareUID.startswith("Direct"):
- yield self._removeInviteShareQuery.on(self._txn, uid=shareUID)
- else:
- # Extract pieces from synthesised UID
- homeID, resourceID = shareUID[len("Direct-"):].split("-")
- # Now remove the binding for the direct share
- yield self._removeDirectShareQuery.on(
- self._txn, homeID=homeID, resourceID=resourceID)
-
-
-class SQLLegacyCalendarShares(SQLLegacyShares):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
- _homeTable = CALENDAR_HOME_TABLE
- _bindTable = CALENDAR_BIND_TABLE
- _homeSchema = schema.CALENDAR_HOME
- _bindSchema = schema.CALENDAR_BIND
- _urlTopSegment = "calendars"
-
-
- def _getHomeWithUID(self, uid):
- return self._txn.calendarHomeWithUID(uid, create=True)
-
-
-
-class SQLLegacyAddressBookShares(SQLLegacyShares):
- """
- Emulator for the implicit interface specified by
- L{twistedcaldav.sharing.InvitesDatabase}.
- """
-
- _homeTable = ADDRESSBOOK_HOME_TABLE
- _bindTable = ADDRESSBOOK_BIND_TABLE
- _homeSchema = schema.ADDRESSBOOK_HOME
- _bindSchema = schema.ADDRESSBOOK_BIND
- _urlTopSegment = "addressbooks"
-
-
- def _getHomeWithUID(self, uid):
- return self._txn.addressbookHomeWithUID(uid, create=True)
-
-
-
class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
def __init__(self, index, cachePool=None):
self.index = index
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/icommondatastore.py 2012-09-28 18:46:24 UTC (rev 9883)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/txdav/common/icommondatastore.py 2012-10-01 12:44:46 UTC (rev 9884)
@@ -246,20 +246,3 @@
A collection resource which may be shared.
"""
- def setSharingUID(shareeUID):
- """
- 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
- that calendar, the main effect of which is to change the per-user uid
- of the properties for that calendar object.
-
- What I{should} be happening is that the calendars just show up in the
- sharee's calendar home, and have a separate methods to determine the
- sharee's and the owner's calendar homes, so the front end can tell it's
- shared.
-
- @param shareeUID: the UID of the sharee.
- @type shareeUID: C{str}
- """
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121001/3357e5ff/attachment-0001.html>
More information about the calendarserver-changes
mailing list