[CalendarServer-changes] [5442] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Apr 7 13:00:48 PDT 2010
Revision: 5442
http://trac.macosforge.org/projects/calendarserver/changeset/5442
Author: cdaboo at apple.com
Date: 2010-04-07 13:00:47 -0700 (Wed, 07 Apr 2010)
Log Message:
-----------
Merged shared calendar support.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/tools/purge.py
CalendarServer/trunk/calendarserver/tools/test/test_principals.py
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/twext/web2/dav/auth.py
CalendarServer/trunk/twext/web2/dav/element/base.py
CalendarServer/trunk/twext/web2/dav/noneprops.py
CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py
CalendarServer/trunk/twext/web2/dav/xattrprops.py
CalendarServer/trunk/twistedcaldav/authkerb.py
CalendarServer/trunk/twistedcaldav/caldavxml.py
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/directory/calendar.py
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/sudo.py
CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
CalendarServer/trunk/twistedcaldav/dropbox.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/freebusyurl.py
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/icaldav.py
CalendarServer/trunk/twistedcaldav/index.py
CalendarServer/trunk/twistedcaldav/instance.py
CalendarServer/trunk/twistedcaldav/mail.py
CalendarServer/trunk/twistedcaldav/memcacheprops.py
CalendarServer/trunk/twistedcaldav/method/delete_common.py
CalendarServer/trunk/twistedcaldav/method/get.py
CalendarServer/trunk/twistedcaldav/method/mkcol.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/method/report_calquery.py
CalendarServer/trunk/twistedcaldav/method/report_common.py
CalendarServer/trunk/twistedcaldav/query/calendarquery.py
CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/schedule.py
CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/scheduling/itip.py
CalendarServer/trunk/twistedcaldav/scheduling/processing.py
CalendarServer/trunk/twistedcaldav/scheduling/utils.py
CalendarServer/trunk/twistedcaldav/sql.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
CalendarServer/trunk/twistedcaldav/test/test_index.py
CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py
CalendarServer/trunk/twistedcaldav/test/test_resource.py
CalendarServer/trunk/twistedcaldav/test/test_xml.py
CalendarServer/trunk/twistedcaldav/test/util.py
CalendarServer/trunk/twistedcaldav/timezoneservice.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/datafilters/
CalendarServer/trunk/twistedcaldav/datafilters/__init__.py
CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py
CalendarServer/trunk/twistedcaldav/datafilters/filter.py
CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py
CalendarServer/trunk/twistedcaldav/datafilters/test/
CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py
CalendarServer/trunk/twistedcaldav/notifications.py
CalendarServer/trunk/twistedcaldav/query/queryfilter.py
CalendarServer/trunk/twistedcaldav/query/test/
CalendarServer/trunk/twistedcaldav/query/test/__init__.py
CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py
CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py
CalendarServer/trunk/twistedcaldav/sharedcalendar.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/test/test_sharing.py
Removed Paths:
-------------
CalendarServer/trunk/twistedcaldav/datafilters/__init__.py
CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py
CalendarServer/trunk/twistedcaldav/datafilters/filter.py
CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py
CalendarServer/trunk/twistedcaldav/datafilters/test/
CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py
CalendarServer/trunk/twistedcaldav/query/test/__init__.py
CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py
CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py
Property Changed:
----------------
CalendarServer/trunk/
Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/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/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/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
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -446,8 +446,9 @@
if config.ProcessType in ('Combined', 'Single'):
- # Memcached is not needed for this process
- config.Memcached.Pools.Default.ClientEnabled = False
+ # Memcached is not needed for the "master" process
+ if config.ProcessType in ('Combined',):
+ config.Memcached.Pools.Default.ClientEnabled = False
# Note: if the master process ever needs access to memcached
# we'll either have to start memcached prior to the
Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/calendarserver/tools/purge.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -16,7 +16,6 @@
# limitations under the License.
##
-from cStringIO import StringIO
from calendarserver.tap.util import FakeRequest
from calendarserver.tap.util import getRootResource
from calendarserver.tools.principals import removeProxy
@@ -35,6 +34,7 @@
from twistedcaldav.config import config, ConfigurationError
from twistedcaldav.directory.directory import DirectoryError, DirectoryRecord
from twistedcaldav.method.delete_common import DeleteResource
+from twistedcaldav.query import queryfilter
import os
import sys
@@ -256,14 +256,15 @@
log.info("Purging events from %d calendar homes" % (len(records),))
filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- TimeRange(start=date,),
- name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
- ),
- name="VCALENDAR",
- )
- )
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ TimeRange(start=date,),
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
+ filter = queryfilter.Filter(filter)
eventCount = 0
for record in records:
@@ -378,6 +379,7 @@
name="VCALENDAR",
)
)
+ filter = queryfilter.Filter(filter)
count = 0
Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_principals.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/calendarserver/tools/test/test_principals.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -81,6 +81,7 @@
cwd = sourceRoot
deferred = Deferred()
+ e = os.environ
reactor.spawnProcess(CapturingProcessProtocol(deferred, None), python, args, env=os.environ, path=cwd)
output = yield deferred
returnValue(output)
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2010-04-07 20:00:47 UTC (rev 5442)
@@ -477,6 +477,26 @@
<key>EnablePrivateEvents</key>
<true/>
+ <!-- Shared Calendars & Address Books -->
+ <key>Sharing</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowExternalUsers</key>
+ <false/>
+ <key>Calendars</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowScheduling</key>
+ <false/>
+ </dict>
+ <key>AddressBooks</key>
+ <dict>
+ <key>Enabled</key>
+ <false/>
+ </dict>
+ </dict>
<!--
Miscellaneous items
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2010-04-07 20:00:47 UTC (rev 5442)
@@ -669,6 +669,26 @@
<key>EnableTimezoneService</key>
<true/>
+ <!-- Shared Calendars & Address Books -->
+ <key>Sharing</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowExternalUsers</key>
+ <false/>
+ <key>Calendars</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowScheduling</key>
+ <false/>
+ </dict>
+ <key>AddressBooks</key>
+ <dict>
+ <key>Enabled</key>
+ <false/>
+ </dict>
+ </dict>
<!--
Miscellaneous items
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/conf/caldavd.plist 2010-04-07 20:00:47 UTC (rev 5442)
@@ -466,7 +466,28 @@
<key>EnablePrivateEvents</key>
<true/>
+ <!-- Shared Calendars & Address Books -->
+ <key>Sharing</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowExternalUsers</key>
+ <false/>
+ <key>Calendars</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>AllowScheduling</key>
+ <false/>
+ </dict>
+ <key>AddressBooks</key>
+ <dict>
+ <key>Enabled</key>
+ <false/>
+ </dict>
+ </dict>
+
<!--
Miscellaneous items
-->
Modified: CalendarServer/trunk/twext/web2/dav/auth.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/auth.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twext/web2/dav/auth.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -96,7 +96,12 @@
pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
d = defer.maybeDeferred(credentials.checkPassword, pswd)
- d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
+ d.addCallback(self._cbPasswordMatch, (
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
return d
##
Modified: CalendarServer/trunk/twext/web2/dav/element/base.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/base.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twext/web2/dav/element/base.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -219,9 +219,9 @@
return child in self.children
- def writeXML(self, output):
- output.write("<?xml version='1.0' encoding='UTF-8'?>")
- self.writeToStream(output, "", 0, True)
+ def writeXML(self, output, pretty=True):
+ output.write("<?xml version='1.0' encoding='UTF-8'?>" + ("\n" if pretty else ""))
+ self.writeToStream(output, "", 0, pretty)
def writeToStream(self, output, ns, level, pretty):
@@ -294,9 +294,9 @@
output.write(" %s='%s'" % (name, value,))
- def toxml(self):
+ def toxml(self, pretty=True):
output = StringIO.StringIO()
- self.writeXML(output)
+ self.writeXML(output, pretty)
return str(output.getvalue())
def element(self, document):
Modified: CalendarServer/trunk/twext/web2/dav/noneprops.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/noneprops.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twext/web2/dav/noneprops.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -49,19 +49,19 @@
def __init__(self, resource):
pass
- def get(self, qname):
+ def get(self, qname, uid=None):
raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "No such property: {%s}%s" % qname))
- def set(self, property):
+ def set(self, property, uid=None):
raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Permission denied for setting property: %s" % (property,)))
- def delete(self, qname):
+ def delete(self, qname, uid=None):
# RFC 2518 Section 12.13.1 says that removal of
# non-existing property is not an error.
pass
- def contains(self, qname):
+ def contains(self, qname, uid=None):
return False
- def list(self):
+ def list(self, uid=None):
return ()
Modified: CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -81,40 +81,42 @@
self._forbiddenTest('get')
- def _makeValue(self):
+ def _makeValue(self, uid=None):
"""
Create and return any old WebDAVDocument for use by the get tests.
"""
- element = Depth("0")
+ element = Depth(uid if uid is not None else "0")
document = WebDAVDocument(element)
return document
- def _setValue(self, originalDocument, value):
+ def _setValue(self, originalDocument, value, uid=None):
element = originalDocument.root_element
attribute = (
self.propertyStore.deadPropertyXattrPrefix +
+ (uid if uid is not None else "") +
"{%s}%s" % element.qname())
self.attrs[attribute] = value
- def _getValue(self, originalDocument):
+ def _getValue(self, originalDocument, uid=None):
element = originalDocument.root_element
attribute = (
self.propertyStore.deadPropertyXattrPrefix +
+ (uid if uid is not None else "") +
"{%s}%s" % element.qname())
return self.attrs[attribute]
- def _checkValue(self, originalDocument):
+ def _checkValue(self, originalDocument, uid=None):
property = originalDocument.root_element.qname()
# Try to load it via xattrPropertyStore.get
- loadedDocument = self.propertyStore.get(property)
+ loadedDocument = self.propertyStore.get(property, uid)
# XXX Why isn't this a WebDAVDocument?
self.assertIsInstance(loadedDocument, Depth)
- self.assertEquals(str(loadedDocument), "0")
+ self.assertEquals(str(loadedDocument), uid if uid else "0")
def test_getXML(self):
@@ -306,3 +308,99 @@
# Make sure that the status is FORBIDDEN, a roughly reasonable mapping
# of the EPERM failure.
self.assertEquals(error.response.code, FORBIDDEN)
+
+ def test_get_uids(self):
+ """
+ L{xattrPropertyStore.get} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self._setValue(document, document.toxml(), uid=uid)
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self._checkValue(document, uid=uid)
+
+
+ def test_set_uids(self):
+ """
+ L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.propertyStore.set(document.root_element, uid=uid)
+ self.assertEquals(
+ decompress(self._getValue(document, uid)), document.toxml())
+
+ def test_delete_uids(self):
+ """
+ L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for delete_uid in (None, "123", "456",):
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.propertyStore.set(document.root_element, uid=uid)
+ self.propertyStore.delete(document.root_element.qname(), uid=delete_uid)
+ self.assertRaises(KeyError, self._getValue, document, uid=delete_uid)
+ for uid in (None, "123", "456",):
+ if uid == delete_uid:
+ continue
+ document = self._makeValue(uid)
+ self.assertEquals(
+ decompress(self._getValue(document, uid)), document.toxml())
+
+ def test_contains_uids(self):
+ """
+ L{xattrPropertyStore.contains} returns C{True} if the given property
+ has a value, C{False} otherwise.
+ """
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.assertFalse(
+ self.propertyStore.contains(document.root_element.qname(), uid=uid))
+ self._setValue(document, document.toxml(), uid=uid)
+ self.assertTrue(
+ self.propertyStore.contains(document.root_element.qname(), uid=uid))
+
+ def test_list_uids(self):
+ """
+ L{xattrPropertyStore.list} returns a C{list} of property names
+ associated with the wrapped file.
+ """
+ prefix = self.propertyStore.deadPropertyXattrPrefix
+ for uid in (None, "123", "456",):
+ user = uid if uid is not None else ""
+ self.attrs[prefix + '%s{foo}bar' % (user,)] = 'baz%s' % (user,)
+ self.attrs[prefix + '%s{bar}baz' % (user,)] = 'quux%s' % (user,)
+ self.attrs[prefix + '%s{moo}mar%s' % (user, user,)] = 'quux%s' % (user,)
+
+ for uid in (None, "123", "456",):
+ user = uid if uid is not None else ""
+ self.assertEquals(
+ set(self.propertyStore.list(uid)),
+ set([
+ (u'foo', u'bar'),
+ (u'bar', u'baz'),
+ (u'moo', u'mar%s' % (user,)),
+ ]))
+
+ self.assertEquals(
+ set(self.propertyStore.list(filterByUID=False)),
+ set([
+ (u'foo', u'bar', None),
+ (u'bar', u'baz', None),
+ (u'moo', u'mar', None),
+ (u'foo', u'bar', "123"),
+ (u'bar', u'baz', "123"),
+ (u'moo', u'mar123', "123"),
+ (u'foo', u'bar', "456"),
+ (u'bar', u'baz', "456"),
+ (u'moo', u'mar456', "456"),
+ ]))
+
Modified: CalendarServer/trunk/twext/web2/dav/xattrprops.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/xattrprops.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twext/web2/dav/xattrprops.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -84,20 +84,29 @@
if sys.platform == "linux2":
deadPropertyXattrPrefix = "user."
- def _encode(clazz, name):
+ def _encode(clazz, name, uid=None):
result = urllib.quote("{%s}%s" % name, safe='{}:')
+ if uid:
+ result = uid + result
r = clazz.deadPropertyXattrPrefix + result
return r
def _decode(clazz, name):
name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
- index = name.find("}")
+ index1 = name.find("{")
+ index2 = name.find("}")
- if (index is -1 or not len(name) > index or not name[0] == "{"):
+ if (index1 is -1 or index2 is -1 or not len(name) > index2):
raise ValueError("Invalid encoded name: %r" % (name,))
+ if index1 == 0:
+ uid = None
+ else:
+ uid = name[:index1]
+ propnamespace = name[index1+1:index2]
+ propname = name[index2+1:]
- return (name[1:index], name[index+1:])
+ return (propnamespace, propname, uid)
_encode = classmethod(_encode)
_decode = classmethod(_decode)
@@ -107,7 +116,7 @@
self.attrs = xattr.xattr(self.resource.fp.path)
- def get(self, qname):
+ def get(self, qname, uid=None):
"""
Retrieve the value of a property stored as an extended attribute on the
wrapped path.
@@ -115,6 +124,8 @@
@param qname: The property to retrieve as a two-tuple of namespace URI
and local name.
+ @param uid: The per-user identifier for per user properties.
+
@raise HTTPError: If there is no value associated with the given
property.
@@ -122,7 +133,7 @@
given property.
"""
try:
- data = self.attrs.get(self._encode(qname))
+ data = self.attrs.get(self._encode(qname, uid))
except KeyError:
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
@@ -176,29 +187,33 @@
return doc.root_element
- def set(self, property):
+ def set(self, property, uid=None):
"""
Store the given property as an extended attribute on the wrapped path.
+ @param uid: The per-user identifier for per user properties.
+
@param property: A L{WebDAVElement} to store.
"""
- key = self._encode(property.qname())
- value = compress(property.toxml())
+ key = self._encode(property.qname(), uid)
+ value = compress(property.toxml(pretty=False))
untilConcludes(setitem, self.attrs, key, value)
# Update the resource because we've modified it
self.resource.fp.restat()
- def delete(self, qname):
+ def delete(self, qname, uid=None):
"""
Remove the extended attribute from the wrapped path which stores the
property given by C{qname}.
+ @param uid: The per-user identifier for per user properties.
+
@param qname: The property to delete as a two-tuple of namespace URI
and local name.
"""
- key = self._encode(qname)
+ key = self._encode(qname, uid)
try:
try:
self.attrs.remove(key)
@@ -214,7 +229,7 @@
"Unable to delete property: " + key))
- def contains(self, qname):
+ def contains(self, qname, uid=None):
"""
Determine whether the property given by C{qname} is stored in an
extended attribute of the wrapped path.
@@ -222,9 +237,11 @@
@param qname: The property to look up as a two-tuple of namespace URI
and local name.
+ @param uid: The per-user identifier for per user properties.
+
@return: C{True} if the property exists, C{False} otherwise.
"""
- key = self._encode(qname)
+ key = self._encode(qname, uid)
try:
self.attrs.get(key)
except KeyError:
@@ -240,11 +257,13 @@
return True
- def list(self):
+ def list(self, uid=None, filterByUID=True):
"""
Enumerate the property names stored in extended attributes of the
wrapped path.
+ @param uid: The per-user identifier for per user properties.
+
@return: A C{list} of property names as two-tuples of namespace URI and
local name.
"""
@@ -257,8 +276,16 @@
statusForFailure(Failure()),
"Unable to list properties: " + self.resource.fp.path))
else:
- return [
+ results = [
self._decode(name)
- for name
- in attrs
- if name.startswith(prefix)]
+ for name in attrs
+ if name.startswith(prefix)
+ ]
+ if filterByUID:
+ return [
+ (namespace, name)
+ for namespace, name, propuid in results
+ if propuid == uid
+ ]
+ else:
+ return results
Modified: CalendarServer/trunk/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/authkerb.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/authkerb.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -179,7 +179,12 @@
self.log_error("%s" % (ex[0],))
raise error.UnauthorizedLogin("Bad credentials for: %s (%s: %s)" % (pcreds.authnURI, ex[0], ex[1],))
else:
- return succeed((pcreds.authnURI, pcreds.authzURI,))
+ return succeed((
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
@@ -307,7 +312,12 @@
creds = pcreds.credentials
if isinstance(creds, NegotiateCredentials):
- return succeed((pcreds.authnURI, pcreds.authzURI,))
+ return succeed((
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -98,41 +98,34 @@
self.start = parse_date_or_datetime(attributes["start"]) if "start" in attributes else None
self.end = parse_date_or_datetime(attributes["end"]) if "end" in attributes else None
+ def valid(self, level=0):
+ """
+ Indicate whether the time-range is valid (must be date-time in UTC).
+
+ @return: True if valid, False otherwise
+ """
+
+ if self.start is not None and not isinstance(self.start, datetime.datetime):
+ log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
+ return False
+ if self.end is not None and not isinstance(self.end, datetime.datetime):
+ log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
+ return False
+ if self.start is not None and self.start.tzinfo != utc:
+ log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
+ return False
+ if self.end is not None and self.end.tzinfo != utc:
+ log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
+ return False
+
+ # No other tests
+ return True
+
class CalDAVTimeZoneElement (CalDAVTextElement):
"""
CalDAV element containing iCalendar data with a single VTIMEZONE component.
"""
- def __init__(self, *children, **attributes):
- super(CalDAVTimeZoneElement, self).__init__(*children, **attributes)
- # An error in the data needs to be reported as a pre-condition error rather than
- # an XML parse error. So this test is moved out of here into a separate method that
- # gets called and can cause the proper WebDAV DAV:error response.
-
- # TODO: Remove the comment above and commented code below once this has been properly tested.
-#
-# try:
-# calendar = self.calendar()
-# if calendar is None: raise ValueError("No data")
-# except ValueError, e:
-# log.err("Invalid iCalendar data (%s): %r" % (calendar, e))
-# raise
-#
-# found = False
-#
-# for subcomponent in calendar.subcomponents():
-# if subcomponent.name() == "VTIMEZONE":
-# if found:
-# raise ValueError("CalDAV:%s may not contain iCalendar data with more than one VTIMEZONE component" % (self.name,))
-# else:
-# found = True
-# else:
-# # FIXME: Spec doesn't seem to really disallow this; it's unclear...
-# raise ValueError("%s component not allowed in CalDAV:timezone data" % (subcomponent.name(),))
-#
-# if not found:
-# raise ValueError("CalDAV:%s must contain iCalendar data with a VTIMEZONE component" % (self.name,))
-
def calendar(self):
"""
Returns a calendar component derived from this element, which contains
@@ -170,64 +163,6 @@
return found
-class CalDAVFilterElement (CalDAVElement):
- """
- CalDAV filter element.
- """
- def __init__(self, *children, **attributes):
- # FIXME: is-defined is obsoleted by CalDAV-access-09. Filter it out here for compatibility.
- children = [c for c in children if c is not None and c.qname() != (caldav_namespace, "is-defined")]
-
- super(CalDAVFilterElement, self).__init__(*children, **attributes)
-
- qualifier = None
- filters = []
-
- for child in self.children:
- qname = child.qname()
-
- if qname in (
- (caldav_namespace, "is-not-defined"),
- (caldav_namespace, "time-range"),
- (caldav_namespace, "text-match"),
- ):
- if qualifier is not None:
- raise ValueError("Only one of CalDAV:time-range, CalDAV:text-match allowed")
- qualifier = child
-
- else:
- filters.append(child)
-
- if qualifier and (qualifier.qname() == (caldav_namespace, "is-not-defined")) and (len(filters) != 0):
- raise ValueError("No other tests allowed when CalDAV:is-not-defined is present")
-
- self.qualifier = qualifier
- self.filters = filters
- self.filter_name = attributes["name"]
- if isinstance(self.filter_name, unicode):
- self.filter_name = self.filter_name.encode("utf-8")
- self.defined = not self.qualifier or (self.qualifier.qname() != (caldav_namespace, "is-not-defined"))
-
- def match(self, item, access=None):
- """
- Returns True if the given calendar item (either a component, property or parameter value)
- matches this filter, False otherwise.
- """
-
- # Always return True for the is-not-defined case as the result of this will
- # be negated by the caller
- if not self.defined: return True
-
- if self.qualifier and not self.qualifier.match(item, access): return False
-
- if len(self.filters) > 0:
- for filter in self.filters:
- if filter._match(item, access):
- return True
- return False
- else:
- return True
-
class CalendarHomeSet (CalDAVElement):
"""
The calendar collections URLs for this principal's calendar user.
@@ -342,7 +277,7 @@
def __init__(self, *children, **attributes):
super(CalendarQuery, self).__init__(*children, **attributes)
- query = None
+ props = None
filter = None
timezone = None
@@ -354,9 +289,9 @@
(davxml.dav_namespace, "propname"),
(davxml.dav_namespace, "prop" ),
):
- if query is not None:
+ if props is not None:
raise ValueError("Only one of CalDAV:allprop, CalDAV:propname, CalDAV:prop allowed")
- query = child
+ props = child
elif qname == (caldav_namespace, "filter"):
filter = child
@@ -371,7 +306,7 @@
if filter is None:
raise ValueError("CALDAV:filter required")
- self.query = query
+ self.props = props
self.filter = filter
self.timezone = timezone
@@ -398,6 +333,8 @@
@classmethod
def fromCalendar(clazz, calendar):
if isinstance(calendar, str):
+ if not calendar:
+ raise ValueError("Missing calendar data")
return clazz(davxml.PCDATAElement(calendar))
elif isinstance(calendar, iComponent):
assert calendar.name() == "VCALENDAR", "Not a calendar: %r" % (calendar,)
@@ -488,205 +425,6 @@
return False
- def elementFromResource(self, resource, timezone=None):
- """
- Return a new CalendarData element comprised of the possibly filtered
- calendar data from the specified resource. If no filter is being applied
- read the data directly from the resource without parsing it. If a filter
- is required, parse the iCal data and filter using this CalendarData.
- @param resource: the resource whose calendar data is to be returned.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: an L{CalendarData} with the (filtered) calendar data.
- """
- return self.elementFromCalendar(resource.iCalendarText(), timezone)
-
- def elementFromCalendar(self, calendar, timezone=None):
- """
- Return a new CalendarData element comprised of the possibly filtered
- calendar.
- @param calendar: the calendar that is to be filtered and returned.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: an L{CalendarData} with the (filtered) calendar data.
- """
-
- # Check for filtering or not
- filtered = self.getFromICalendar(calendar, timezone)
- return CalendarData.fromCalendar(filtered)
-
- def elementFromResourceWithAccessRestrictions(self, resource, access, timezone=None):
- """
- Return a new CalendarData element comprised of the possibly filtered
- calendar data from the specified resource. If no filter is being applied
- read the data directly from the resource without parsing it. If a filter
- is required, parse the iCal data and filter using this CalendarData.
-
- Also, apply appropriate access restriction filtering to the data.
-
- @param resource: the resource whose calendar data is to be returned.
- @param access: private event access restriction level.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: an L{CalendarData} with the (filtered) calendar data.
- """
- return self.elementFromCalendarWithAccessRestrictions(resource.iCalendarText(), access, timezone)
-
- def elementFromCalendarWithAccessRestrictions(self, calendar, access, timezone=None):
- """
- Return a new CalendarData element comprised of the possibly filtered
- calendar.
-
- Also, apply appropriate access restriction filtering to the data.
-
- @param calendar: the calendar that is to be filtered and returned.
- @param access: private event access restriction level.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: an L{CalendarData} with the (filtered) calendar data.
- """
-
- # Do normal filtering first
- filtered_calendar = self.getFromICalendar(calendar, timezone)
-
- if access in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
- # Create a CALDAV:calendar-data element with the appropriate iCalendar Component/Property
- # filter in place for the access restriction in use
-
- extra_access = ()
- if access == iComponent.ACCESS_RESTRICTED:
- extra_access = (
- Property(name="SUMMARY"),
- Property(name="LOCATION"),
- )
-
- filter = CalendarData(
- CalendarComponent(
-
- # VCALENDAR properties
- Property(name="PRODID"),
- Property(name="VERSION"),
- Property(name="CALSCALE"),
- Property(name=iComponent.ACCESS_PROPERTY),
-
- # VEVENT
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="TRANSP"),
- Property(name="DTSTART"),
- Property(name="DTEND"),
- Property(name="DURATION"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VEVENT"}
- ),
-
- # VTODO
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="DTSTART"),
- Property(name="COMPLETED"),
- Property(name="DUE"),
- Property(name="DURATION"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VTODO"}
- ),
-
- # VJOURNAL
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="TRANSP"),
- Property(name="DTSTART"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VJOURNAL"}
- ),
-
- # VFREEBUSY
- CalendarComponent(
- Property(name="UID"),
- Property(name="DTSTAMP"),
- Property(name="DTSTART"),
- Property(name="DTEND"),
- Property(name="DURATION"),
- Property(name="FREEBUSY"),
- *extra_access,
- **{"name":"VFREEBUSY"}
- ),
-
- # VTIMEZONE
- CalendarComponent(
- AllProperties(),
- AllComponents(),
- name="VTIMEZONE",
- ),
- name="VCALENDAR",
- ),
- )
-
- # Now "filter" the resource calendar data through the CALDAV:calendar-data element
- return filter.elementFromCalendar(filtered_calendar, timezone)
- else:
- return CalendarData.fromCalendar(filtered_calendar)
-
- def getFromICalendar(self, calendar, timezone=None):
- """
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
-
- Returns a calendar object containing the data in the given calendar
- which is specified by this CalendarData.
- """
- if calendar is None or isinstance(calendar, str) and not calendar:
- raise ValueError("Not a calendar: %r" % (calendar,))
-
- # Empty element: get all data
- if not self.children: return calendar
-
- # If we were passed a string, parse it out as a Component
- if isinstance(calendar, str):
- try:
- calendar = iComponent.fromString(calendar)
- except ValueError:
- raise ValueError("Not a calendar: %r" % (calendar,))
-
- if calendar is None or calendar.name() != "VCALENDAR":
- raise ValueError("Not a calendar: %r" % (calendar,))
-
- # Pre-process the calendar data based on expand and limit options
- if self.freebusy_set:
- calendar = self.limitFreeBusy(calendar)
-
- # Filter data based on any provided CALDAV:comp element, or use all current data
- if self.component is not None:
- calendar = self.component.getFromICalendar(calendar)
-
- # Post-process the calendar data based on the expand and limit options
- if self.recurrence_set:
- if isinstance(self.recurrence_set, LimitRecurrenceSet):
- calendar = self.limitRecurrence(calendar)
- elif isinstance(self.recurrence_set, Expand):
- calendar = self.expandRecurrence(calendar, timezone)
-
- return calendar
-
def calendar(self):
"""
Returns a calendar component derived from this element.
@@ -710,55 +448,6 @@
return str(data)
- def expandRecurrence(self, calendar, timezone=None):
- """
- Expand the recurrence set into individual items.
- @param calendar: the L{Component} for the calendar to operate on.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: the L{Component} for the result.
- """
- return calendar.expand(self.recurrence_set.start, self.recurrence_set.end, timezone)
-
- def limitRecurrence(self, calendar):
- """
- Limit the set of overridden instances returned to only those
- that are needed to describe the range of instances covered
- by the specified time range.
- @param calendar: the L{Component} for the calendar to operate on.
- @return: the L{Component} for the result.
- """
- raise NotImplementedError()
- return calendar
-
- def limitFreeBusy(self, calendar):
- """
- Limit the range of any FREEBUSY properties in the calendar, returning
- a new calendar if limits were applied, or the same one if no limits were applied.
- @param calendar: the L{Component} for the calendar to operate on.
- @return: the L{Component} for the result.
- """
-
- # First check for any VFREEBUSYs - can ignore limit if there are none
- if calendar.mainType() != "VFREEBUSY":
- return calendar
-
- # Create duplicate calendar and filter FREEBUSY properties
- calendar = calendar.duplicate()
- for component in calendar.subcomponents():
- if component.name() != "VFREEBUSY":
- continue
- for property in component.properties("FREEBUSY"):
- newvalue = []
- for period in property.value():
- clipped = clipPeriod(period, (self.freebusy_set.start, self.freebusy_set.end))
- if clipped:
- newvalue.append(clipped)
- if len(newvalue):
- property.setValue(newvalue)
- else:
- component.removeProperty(property)
- return calendar
-
class CalendarComponent (CalDAVElement):
"""
Defines which component types to return.
@@ -936,79 +625,7 @@
allowed_children = { (caldav_namespace, "comp-filter"): (1, 1) }
- def match(self, component, access=None):
- """
- Returns True if the given calendar component matches this filter, False
- otherwise.
- """
-
- # We only care about certain access restrictions.
- if access not in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
- access = None
-
- # We need to prepare ourselves for a time-range query by pre-calculating
- # the set of instances up to the latest time-range limit. That way we can
- # avoid having to do some form of recurrence expansion for each query sub-part.
- maxend, isStartTime = self.getmaxtimerange()
- if maxend:
- if isStartTime:
- if component.isRecurringUnbounded():
- # Unbounded recurrence is always within a start-only time-range
- instances = None
- else:
- # Expand the instances up to infinity
- instances = component.expandTimeRanges(datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc), ignoreInvalidInstances=True)
- else:
- instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
- else:
- instances = None
- self.children[0].setInstances(instances)
-
- # <filter> contains exactly one <comp-filter>
- return self.children[0].match(component, access)
-
- def valid(self):
- """
- Indicate whether this filter element's structure is valid wrt iCalendar
- data object model.
-
- @return: True if valid, False otherwise
- """
-
- # Must have one child element for VCALENDAR
- return self.children[0].valid(0)
-
- def settimezone(self, tzelement):
- """
- Set the default timezone to use with this query.
- @param calendar: a L{Component} for the VCALENDAR containing the one
- VTIMEZONE that we want
- @return: the L{datetime.tzinfo} derived from the VTIMEZONE or utc.
- """
- assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
-
- if tzelement is not None:
- calendar = tzelement.calendar()
- if calendar is not None:
- for subcomponent in calendar.subcomponents():
- if subcomponent.name() == "VTIMEZONE":
- # <filter> contains exactly one <comp-filter>
- tzinfo = subcomponent.gettzinfo()
- self.children[0].settzinfo(tzinfo)
- return tzinfo
-
- # Default to using utc tzinfo
- self.children[0].settzinfo(utc)
- return utc
-
- def getmaxtimerange(self):
- """
- Get the date farthest into the future in any time-range elements
- """
-
- return self.children[0].getmaxtimerange(None, False)
-
-class ComponentFilter (CalDAVFilterElement):
+class ComponentFilter (CalDAVElement):
"""
Limits a search to only the chosen component types.
(CalDAV-access-09, section 9.6.1)
@@ -1016,7 +633,6 @@
name = "comp-filter"
allowed_children = {
- (caldav_namespace, "is-defined" ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
(caldav_namespace, "is-not-defined" ): (0, 1),
(caldav_namespace, "time-range" ): (0, 1),
(caldav_namespace, "comp-filter" ): (0, None),
@@ -1024,153 +640,7 @@
}
allowed_attributes = { "name": True }
- def match(self, item, access):
- """
- Returns True if the given calendar item (which is a component)
- matches this filter, False otherwise.
- This specialization uses the instance matching option of the time-range filter
- to minimize instance expansion.
- """
-
- # Always return True for the is-not-defined case as the result of this will
- # be negated by the caller
- if not self.defined: return True
-
- if self.qualifier and not self.qualifier.matchinstance(item, self.instances): return False
-
- if len(self.filters) > 0:
- for filter in self.filters:
- if filter._match(item, access):
- return True
- return False
- else:
- return True
-
- def _match(self, component, access):
- # At least one subcomponent must match (or is-not-defined is set)
- for subcomponent in component.subcomponents():
- # If access restrictions are in force, restrict matching to specific components only.
- # In particular do not match VALARM.
- if access and subcomponent.name() not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE",):
- continue
-
- # Try to match the component name
- if isinstance(self.filter_name, str):
- if subcomponent.name() != self.filter_name: continue
- else:
- if subcomponent.name() not in self.filter_name: continue
- if self.match(subcomponent, access): break
- else:
- return not self.defined
- return self.defined
-
- def setInstances(self, instances):
- """
- Give the list of instances to each comp-filter element.
- @param instances: the list of instances.
- """
- self.instances = instances
- for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
- compfilter.setInstances(instances)
-
- def valid(self, level):
- """
- Indicate whether this filter element's structure is valid wrt iCalendar
- data object model.
-
- @param level: the nesting level of this filter element, 0 being the top comp-filter.
- @return: True if valid, False otherwise
- """
-
- # Check for time-range
- timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-
- if level == 0:
- # Must have VCALENDAR at the top
- if (self.filter_name != "VCALENDAR") or timerange:
- log.msg("Top-level comp-filter must be VCALENDAR, instead: %s" % (self.filter_name,))
- return False
- elif level == 1:
- # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
- if self.filter_name in ("VCALENDAR", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE"):
- log.msg("comp-filter wrong component type: %s" % (self.filter_name,))
- return False
-
- # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
- if timerange and self.filter_name not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY"):
- log.msg("time-range cannot be used with component %s" % (self.filter_name,))
- return False
- elif level == 2:
- # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
- if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY")):
- log.msg("comp-filter wrong sub-component type: %s" % (self.filter_name,))
- return False
-
- # time-range only on VALARM, AVAILABLE
- if timerange and self.filter_name not in ("VALARM", "AVAILABLE",):
- log.msg("time-range cannot be used with sub-component %s" % (self.filter_name,))
- return False
- else:
- # Disallow all standard iCal components anywhere else
- if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE")) or timerange:
- log.msg("comp-filter wrong standard component type: %s" % (self.filter_name,))
- return False
-
- # Test each property
- for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
- if not propfilter.valid():
- return False
-
- # Test each component
- for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
- if not compfilter.valid(level + 1):
- return False
-
- # Test the time-range
- if timerange:
- if not self.qualifier.valid():
- return False
-
- return True
-
- def settzinfo(self, tzinfo):
- """
- Set the default timezone to use with this query.
- @param tzinfo: a L{datetime.tzinfo} to use.
- """
-
- # Give tzinfo to any TimeRange we have
- if isinstance(self.qualifier, TimeRange):
- self.qualifier.settzinfo(tzinfo)
-
- # Pass down to sub components/properties
- for x in self.filters:
- x.settzinfo(tzinfo)
-
- def getmaxtimerange(self, currentMaximum, currentIsStartTime):
- """
- Get the date furthest into the future in any time-range elements
-
- @param currentMaximum: current future value to compare with
- @type currentMaximum: L{datetime.datetime}
- """
-
- # Give tzinfo to any TimeRange we have
- isStartTime = False
- if isinstance(self.qualifier, TimeRange):
- isStartTime = self.qualifier.end is None
- compareWith = self.qualifier.start if isStartTime else self.qualifier.end
- if currentMaximum is None or currentMaximum < compareWith:
- currentMaximum = compareWith
- currentIsStartTime = isStartTime
-
- # Pass down to sub components/properties
- for x in self.filters:
- currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
-
- return currentMaximum, currentIsStartTime
-
-class PropertyFilter (CalDAVFilterElement):
+class PropertyFilter (CalDAVElement):
"""
Limits a search to specific properties.
(CalDAV-access-09, section 9.6.2)
@@ -1178,7 +648,6 @@
name = "prop-filter"
allowed_children = {
- (caldav_namespace, "is-defined" ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
(caldav_namespace, "is-not-defined" ): (0, 1),
(caldav_namespace, "time-range" ): (0, 1),
(caldav_namespace, "text-match" ): (0, 1),
@@ -1186,80 +655,7 @@
}
allowed_attributes = { "name": True }
- def _match(self, component, access):
- # When access restriction is in force, we need to only allow matches against the properties
- # allowed by the access restriction level.
- if access:
- allowedProperties = iComponent.confidentialPropertiesMap.get(component.name(), None)
- if allowedProperties and access == iComponent.ACCESS_RESTRICTED:
- allowedProperties += iComponent.extraRestrictedProperties
- else:
- allowedProperties = None
-
- # At least one property must match (or is-not-defined is set)
- for property in component.properties():
- # Apply access restrictions, if any.
- if allowedProperties is not None and property.name() not in allowedProperties:
- continue
- if property.name() == self.filter_name and self.match(property, access): break
- else:
- return not self.defined
- return self.defined
-
- def valid(self):
- """
- Indicate whether this filter element's structure is valid wrt iCalendar
- data object model.
-
- @return: True if valid, False otherwise
- """
-
- # Check for time-range
- timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-
- # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
- if timerange and self.filter_name not in ("COMPLETED", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
- log.msg("time-range cannot be used with property %s" % (self.filter_name,))
- return False
-
- # Test the time-range
- if timerange:
- if not self.qualifier.valid():
- return False
-
- # No other tests
- return True
-
- def settzinfo(self, tzinfo):
- """
- Set the default timezone to use with this query.
- @param tzinfo: a L{datetime.tzinfo} to use.
- """
-
- # Give tzinfo to any TimeRange we have
- if isinstance(self.qualifier, TimeRange):
- self.qualifier.settzinfo(tzinfo)
-
- def getmaxtimerange(self, currentMaximum, currentIsStartTime):
- """
- Get the date furthest into the future in any time-range elements
-
- @param currentMaximum: current future value to compare with
- @type currentMaximum: L{datetime.datetime}
- """
-
- # Give tzinfo to any TimeRange we have
- isStartTime = False
- if isinstance(self.qualifier, TimeRange):
- isStartTime = self.qualifier.end is None
- compareWith = self.qualifier.start if isStartTime else self.qualifier.end
- if currentMaximum is None or currentMaximum < compareWith:
- currentMaximum = compareWith
- currentIsStartTime = isStartTime
-
- return currentMaximum, currentIsStartTime
-
-class ParameterFilter (CalDAVFilterElement):
+class ParameterFilter (CalDAVElement):
"""
Limits a search to specific parameters.
(CalDAV-access-09, section 9.6.3)
@@ -1267,42 +663,11 @@
name = "param-filter"
allowed_children = {
- (caldav_namespace, "is-defined" ): (0, 1), # FIXME: obsoleted in CalDAV-access-09
(caldav_namespace, "is-not-defined" ): (0, 1),
(caldav_namespace, "text-match" ): (0, 1),
}
allowed_attributes = { "name": True }
- def _match(self, property, access):
- # We have to deal with the problem that the 'Native' form of a property
- # will be missing the TZID parameter due to the conversion performed. Converting
- # to non-native for the entire calendar object causes problems elsewhere, so its
- # best to do it here for this one special case.
- if self.filter_name == "TZID":
- transformed = property.transformAllFromNative()
- else:
- transformed = False
-
- # At least one property must match (or is-not-defined is set)
- result = not self.defined
- for parameterName in property.params().keys():
- if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
- result = self.defined
- break
-
- if transformed:
- property.transformAllToNative()
- return result
-
-class IsDefined (CalDAVEmptyElement):
- """
- FIXME: Removed from spec.
- """
- name = "is-defined"
-
- def match(self, component, access):
- return component is not None
-
class IsNotDefined (CalDAVEmptyElement):
"""
Specifies that the named iCalendar item does not exist.
@@ -1310,13 +675,6 @@
"""
name = "is-not-defined"
- def match(self, component, access):
- # Oddly, this needs always to return True so that it appears there is
- # a match - but we then "negate" the result if is-not-defined is set.
- # Actually this method should never be called as we special case the
- # is-not-defined option.
- return True
-
class TextMatch (CalDAVTextElement):
"""
Specifies a substring match on a property or parameter value.
@@ -1344,68 +702,6 @@
"negate-condition": False
}
- def __init__(self, *children, **attributes):
- super(TextMatch, self).__init__(*children, **attributes)
-
- if "caseless" in attributes:
- caseless = attributes["caseless"]
- if caseless == "yes":
- self.caseless = True
- elif caseless == "no":
- self.caseless = False
- else:
- self.caseless = True
-
- if "negate-condition" in attributes:
- negate = attributes["negate-condition"]
- if negate == "yes":
- self.negate = True
- elif caseless == "no":
- self.negate = False
- else:
- self.negate = False
-
- def match(self, item, access):
- """
- Match the text for the item.
- If the item is a property, then match the property value,
- otherwise it may be a list of parameter values - try to match anyone of those
- """
- if item is None: return False
-
- if isinstance(item, iProperty):
- values = [item.value()]
- else:
- values = item
-
- test = unicode(str(self), "utf-8")
- if self.caseless:
- test = test.lower()
-
- def _textCompare(s):
- if self.caseless:
- if s.lower().find(test) != -1:
- return True, not self.negate
- else:
- if s.find(test) != -1:
- return True, not self.negate
- return False, False
-
- for value in values:
- # NB Its possible that we have a text list value which appears as a Python list,
- # so we need to check for that an iterate over the list.
- if isinstance(value, list):
- for subvalue in value:
- matched, result = _textCompare(subvalue)
- if matched:
- return result
- else:
- matched, result = _textCompare(value)
- if matched:
- return result
-
- return self.negate
-
class TimeZone (CalDAVTimeZoneElement):
"""
Specifies a time zone component.
@@ -1420,103 +716,6 @@
"""
name = "time-range"
- def __init__(self, *children, **attributes):
- super(TimeRange, self).__init__(*children, **attributes)
- self.tzinfo = None
-
- def settzinfo(self, tzinfo):
- """
- Set the default timezone to use with this query.
- @param tzinfo: a L{datetime.tzinfo} to use.
- """
-
- # Give tzinfo to any TimeRange we have
- self.tzinfo = tzinfo
-
- def valid(self):
- """
- Indicate whether the time-range is valid (must be date-time in UTC).
-
- @return: True if valid, False otherwise
- """
-
- if self.start is not None and not isinstance(self.start, datetime.datetime):
- log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
- return False
- if self.end is not None and not isinstance(self.end, datetime.datetime):
- log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
- return False
- if self.start is not None and self.start.tzinfo != utc:
- log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
- return False
- if self.end is not None and self.end.tzinfo != utc:
- log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
- return False
-
- # No other tests
- return True
-
- def match(self, property, access):
- """
- NB This is only called when doing a time-range match on a property.
- """
- if property is None:
- return False
- else:
- return property.containsTimeRange(self.start, self.end, self.tzinfo)
-
- def matchinstance(self, component, instances):
- """
- Test whether this time-range element causes a match to the specified component
- using the specified set of instances to determine the expanded time ranges.
- @param component: the L{Component} to test.
- @param instances: the list of expanded instances.
- @return: True if the time-range query matches, False otherwise.
- """
- if component is None:
- return False
-
- assert instances is not None or self.end is None, "Failure to expand instance for time-range filter: %r" % (self,)
-
- # Special case open-ended unbounded
- if instances is None:
- if component.getRecurrenceIDUTC() is None:
- return True
- else:
- # See if the overridden component's start is past the start
- start, _ignore_end = component.getEffectiveStartEnd()
- if start is None:
- return True
- else:
- return start >= self.start
-
- # Handle alarms as a special case
- alarms = (component.name() == "VALARM")
- if alarms:
- testcomponent = component._parent
- else:
- testcomponent = component
-
- for key in instances:
- instance = instances[key]
-
- # First make sure components match
- if not testcomponent.same(instance.component):
- continue
-
- if alarms:
- # Get all the alarm triggers for this instance and test each one
- triggers = instance.getAlarmTriggers()
- for trigger in triggers:
- if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
- return True
- else:
- # Regular instance overlap test
- if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
- return True
-
- return False
-
class CalendarMultiGet (CalDAVElement):
"""
CalDAV report used to retrieve specific calendar component items via their
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
from twext.web2.dav.element.base import twisted_private_namespace
from twext.web2.dav import davxml
+from twistedcaldav import caldavxml
from twistedcaldav.ical import Component as iComponent
from vobject.icalendar import utc
@@ -49,10 +50,14 @@
"calendarserver-private-comments",
)
-calendarserver_principal_property_search = (
+calendarserver_principal_property_search_compliance = (
"calendarserver-principal-property-search",
)
+calendarserver_sharing_compliance = (
+ "calendarserver-sharing",
+)
+
class TwistedCalendarAccessProperty (davxml.WebDAVTextElement):
"""
Contains the calendar access level (private events) for the resource.
@@ -571,7 +576,310 @@
namespace = calendarserver_namespace
name = "auto-schedule"
+class ReadAccess (davxml.WebDAVEmptyElement):
+ """
+ Denotes read and update attendee partstat on a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "read"
+
+class ReadWriteAccess (davxml.WebDAVEmptyElement):
+ """
+ Denotes read and write access on a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "read-write"
+
+class ReadWriteScheduleAccess (davxml.WebDAVEmptyElement):
+ """
+ Denotes read and write and schedule access on a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "read-write-schedule"
+
+class UID (davxml.WebDAVTextElement):
+ namespace = calendarserver_namespace
+ name = "uid"
+
+class InReplyTo (davxml.WebDAVTextElement):
+ namespace = calendarserver_namespace
+ name = "in-reply-to"
+
##
+# Notifications
+##
+
+class SharedOwner (davxml.WebDAVEmptyElement):
+ """
+ Denotes a shared collection.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-owner"
+
+class Shared (davxml.WebDAVEmptyElement):
+ """
+ Denotes a shared collection.
+ """
+ namespace = calendarserver_namespace
+ name = "shared"
+
+class Subscribed (davxml.WebDAVEmptyElement):
+ """
+ Denotes a subscribed calendar collection.
+ """
+ namespace = calendarserver_namespace
+ name = "subscribed"
+
+class SharedURL (davxml.WebDAVTextElement):
+ """
+ The source url for a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-url"
+
+class SharedCalendar (davxml.WebDAVElement):
+ """
+ The url for a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-calendar"
+
+ allowed_children = {
+ davxml.HRef.qname() : (1, 1),
+ }
+
+class SharedAcceptEmailNotification (davxml.WebDAVTextElement):
+ """
+ The accept email flag for a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-accept-email-notification"
+
+class Birthday (davxml.WebDAVEmptyElement):
+ """
+ Denotes a birthday calendar collection.
+ """
+ namespace = calendarserver_namespace
+ name = "birthday"
+
+class InviteShare (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "share"
+
+ allowed_children = {
+ (calendarserver_namespace, "set" ) : (0, None),
+ (calendarserver_namespace, "remove" ) : (0, None),
+ }
+
+class InviteSet (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "set"
+
+ allowed_children = {
+ (dav_namespace, "href" ) : (1, 1),
+ (calendarserver_namespace, "summary" ) : (0, 1),
+ (calendarserver_namespace, "read" ) : (0, 1),
+ (calendarserver_namespace, "read-write" ) : (0, 1),
+ (calendarserver_namespace, "read-write-schedule" ) : (0, 1),
+ }
+
+class InviteRemove (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "remove"
+
+ allowed_children = {
+ (dav_namespace, "href" ) : (1, 1),
+ (calendarserver_namespace, "read" ) : (0, 1),
+ (calendarserver_namespace, "read-write" ) : (0, 1),
+ (calendarserver_namespace, "read-write-schedule" ) : (0, 1),
+ }
+
+class InviteUser (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "user"
+
+ allowed_children = {
+ (calendarserver_namespace, "href" ) : (1, 1),
+ (calendarserver_namespace, "invite-noresponse" ) : (0, 1),
+ (calendarserver_namespace, "invite-deleted" ) : (0, 1),
+ (calendarserver_namespace, "invite-accepted" ) : (0, 1),
+ (calendarserver_namespace, "invite-declined" ) : (0, 1),
+ (calendarserver_namespace, "invite-invalid" ) : (0, 1),
+ (calendarserver_namespace, "access" ) : (1, 1),
+ (calendarserver_namespace, "summary" ) : (0, 1),
+ }
+
+class InviteAccess (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "access"
+
+ allowed_children = {
+ (calendarserver_namespace, "read" ) : (0, 1),
+ (calendarserver_namespace, "read-write" ) : (0, 1),
+ (calendarserver_namespace, "read-write-schedule" ): (0, 1),
+ }
+
+class Invite (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "invite"
+
+ allowed_children = {
+ (calendarserver_namespace, "user" ) : (0, None),
+ }
+
+class InviteSummary (davxml.WebDAVTextElement):
+ namespace = calendarserver_namespace
+ name = "summary"
+
+class InviteStatusNoResponse (davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "invite-noresponse"
+
+class InviteStatusDeleted (davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "invite-deleted"
+
+class InviteStatusAccepted (davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "invite-accepted"
+
+class InviteStatusDeclined (davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "invite-declined"
+
+class InviteStatusInvalid (davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "invite-invalid"
+
+class HostURL (davxml.WebDAVElement):
+ """
+ The source for a shared calendar
+ """
+ namespace = calendarserver_namespace
+ name = "hosturl"
+
+ allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class Organizer (davxml.WebDAVElement):
+ """
+ The organizer for a shared calendar
+ """
+ namespace = calendarserver_namespace
+ name = "organizer"
+
+ allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class InviteNotification (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "invite-notification"
+
+ allowed_children = {
+ UID.qname() : (0, 1),
+ (dav_namespace, "href") : (0, 1),
+ InviteStatusNoResponse.qname() : (0, 1),
+ InviteStatusDeleted.qname() : (0, 1),
+ InviteStatusAccepted.qname() : (0, 1),
+ InviteStatusDeclined.qname() : (0, 1),
+ InviteAccess.qname() : (0, 1),
+ HostURL.qname() : (0, 1),
+ Organizer.qname() : (0, 1),
+ InviteSummary.qname() : (0, 1),
+ }
+
+ allowed_attributes = {
+ "shared-type" : True,
+ }
+
+class InviteReply (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "invite-reply"
+
+ allowed_children = {
+ (dav_namespace, "href") : (0, 1),
+ InviteStatusAccepted.qname() : (0, 1),
+ InviteStatusDeclined.qname() : (0, 1),
+ HostURL.qname() : (0, 1),
+ InReplyTo.qname() : (0, 1),
+ InviteSummary.qname() : (0, 1),
+ }
+
+class ResourceUpdateAdded(davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "resource-added-notification"
+
+class ResourceUpdateUpdated(davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "resource-updated-notification"
+
+class ResourceDeletedUpdated(davxml.WebDAVEmptyElement):
+ namespace = calendarserver_namespace
+ name = "resource-deleted-notification"
+
+class ResourceUpdateNotification (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "resource-update-notification"
+
+ allowed_children = {
+ (dav_namespace, "href") : (0, 1),
+ UID.qname() : (0, 1),
+ ResourceUpdateAdded.qname() : (0, 1),
+ ResourceUpdateUpdated.qname() : (0, 1),
+ ResourceDeletedUpdated.qname() : (0, 1),
+ }
+
+class SharedCalendarUpdateNotification (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "shared-update-notification"
+
+ allowed_children = {
+ HostURL.qname() : (0, 1), # The shared calendar url
+ (dav_namespace, "href") : (0, 1), # Email userid that was invited
+ InviteStatusDeleted.qname() : (0, 1), # What the user did...
+ InviteStatusAccepted.qname() : (0, 1),
+ InviteStatusDeclined.qname() : (0, 1),
+ }
+
+class Notification (davxml.WebDAVElement):
+ """
+ Denotes a notification collection, or a notification message.
+ """
+ namespace = calendarserver_namespace
+ name = "notification"
+
+ allowed_children = {
+ DTStamp.qname() : (0, None),
+ InviteNotification.qname() : (0, None),
+ InviteReply.qname() : (0, None),
+ ResourceUpdateNotification.qname() : (0, None),
+ SharedCalendarUpdateNotification.qname() : (0, None),
+ }
+
+class NotificationURL (davxml.WebDAVElement):
+ """
+ A principal property to indicate the notification collection for the principal.
+ """
+ namespace = calendarserver_namespace
+ name = "notification-URL"
+ hidden = True
+ protected = True
+
+ allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class NotificationType (davxml.WebDAVElement):
+ """
+ A property to indicate what type of notification the resource represents.
+ """
+ namespace = calendarserver_namespace
+ name = "notification-type"
+ hidden = True
+ protected = True
+
+ allowed_children = {
+ InviteNotification.qname() : (0, None),
+ InviteReply.qname() : (0, None),
+ }
+
+##
# Extensions to davxml.ResourceType
##
@@ -582,3 +890,5 @@
davxml.ResourceType.timezones = davxml.ResourceType(Timezones())
davxml.ResourceType.ischeduleinbox = davxml.ResourceType(IScheduleInbox())
davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())
+davxml.ResourceType.notification = davxml.ResourceType(davxml.Collection(), Notification())
+davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/__init__.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,47 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from vobject.base import registerBehavior
-from vobject.icalendar import VCalendarComponentBehavior, VCalendar2_0
-
-"""
-Data filtering module.
-"""
-
-# This is where we register our special components with vobject
-
-class X_CALENDARSERVER_PERUSER(VCalendarComponentBehavior):
- name='X-CALENDARSERVER-PERUSER'
- description='A component used to encapsulate per-user data.'
- sortFirst = ('uid', 'x-calendarserver-peruser-uid')
- knownChildren = {
- 'UID': (1, 1, None),#min, max, behaviorRegistry id
- 'X-CALENDARSERVER-PERUSER-UID': (1, 1, None),
- 'X-CALENDARSERVER-PERINSTANCE': (0, None, None),
- }
-
-registerBehavior(X_CALENDARSERVER_PERUSER)
-VCalendar2_0.knownChildren['X-CALENDARSERVER-PERUSER'] = (0, None, None)
-
-class X_CALENDARSERVER_PERINSTANCE(VCalendarComponentBehavior):
- name='X-CALENDARSERVER-PERINSTANCE'
- description='A component used to encapsulate per-user instance data.'
- sortFirst = ('recurrence-id',)
- knownChildren = {
- 'RECURRENCE-ID':(0, 1, None),#min, max, behaviorRegistry id
- }
-
-registerBehavior(X_CALENDARSERVER_PERINSTANCE)
Copied: CalendarServer/trunk/twistedcaldav/datafilters/__init__.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,47 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from vobject.base import registerBehavior
+from vobject.icalendar import VCalendarComponentBehavior, VCalendar2_0
+
+"""
+Data filtering module.
+"""
+
+# This is where we register our special components with vobject
+
+class X_CALENDARSERVER_PERUSER(VCalendarComponentBehavior):
+ name='X-CALENDARSERVER-PERUSER'
+ description='A component used to encapsulate per-user data.'
+ sortFirst = ('uid', 'x-calendarserver-peruser-uid')
+ knownChildren = {
+ 'UID': (1, 1, None),#min, max, behaviorRegistry id
+ 'X-CALENDARSERVER-PERUSER-UID': (1, 1, None),
+ 'X-CALENDARSERVER-PERINSTANCE': (0, None, None),
+ }
+
+registerBehavior(X_CALENDARSERVER_PERUSER)
+VCalendar2_0.knownChildren['X-CALENDARSERVER-PERUSER'] = (0, None, None)
+
+class X_CALENDARSERVER_PERINSTANCE(VCalendarComponentBehavior):
+ name='X-CALENDARSERVER-PERINSTANCE'
+ description='A component used to encapsulate per-user instance data.'
+ sortFirst = ('recurrence-id',)
+ knownChildren = {
+ 'RECURRENCE-ID':(0, 1, None),#min, max, behaviorRegistry id
+ }
+
+registerBehavior(X_CALENDARSERVER_PERINSTANCE)
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/calendardata.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,172 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.caldavxml import LimitRecurrenceSet, Expand, AllComponents,\
- AllProperties
-from twistedcaldav.datafilters.filter import CalendarFilter
-from twistedcaldav.dateops import clipPeriod
-from twistedcaldav.ical import Component
-
-__all__ = [
- "CalendarDataFilter",
-]
-
-class CalendarDataFilter(CalendarFilter):
- """
- Filter using the CALDAV:calendar-data element specification
- """
-
- def __init__(self, calendardata, timezone=None):
- """
-
- @param calendardata: the XML element describing how to filter
- @type calendardata: L{CalendarData}
- @param timezone: the VTIMEZONE to use for floating/all-day
- @type timezone: L{Component}
- """
-
- self.calendardata = calendardata
- self.timezone = timezone
-
- def filter(self, ical):
- """
- Filter the supplied iCalendar (vobject) data using the request information.
-
- @param ical: iCalendar object
- @type ical: L{Component} or C{str}
-
- @return: L{Component} for the filtered calendar data
- """
-
- # Empty element: get all data
- if not self.calendardata.children:
- return ical
-
- # Make sure input is valid
- ical = self.validCalendar(ical)
-
- # Pre-process the calendar data based on expand and limit options
- if self.calendardata.freebusy_set:
- ical = self.limitFreeBusy(ical)
-
- # Filter data based on any provided CALDAV:comp element, or use all current data
- if self.calendardata.component is not None:
- ical = self.compFilter(self.calendardata.component, ical)
-
- # Post-process the calendar data based on the expand and limit options
- if self.calendardata.recurrence_set:
- if isinstance(self.calendardata.recurrence_set, LimitRecurrenceSet):
- ical = self.limitRecurrence(ical)
- elif isinstance(self.calendardata.recurrence_set, Expand):
- ical = self.expandRecurrence(ical, self.timezone)
-
- return ical
-
- def compFilter(self, comp, component):
- """
- Returns a calendar component object containing the data in the given
- component which is specified by this CalendarComponent.
- """
- if comp.type != component.name():
- raise ValueError("%s of type %r can't get data from component of type %r"
- % (comp.sname(), comp.type, component.name()))
-
- result = Component(comp.type)
-
- xml_components = comp.components
- xml_properties = comp.properties
-
- # Empty element means do all properties and components
- if xml_components is None and xml_properties is None:
- xml_components = AllComponents()
- xml_properties = AllProperties()
-
- if xml_components is not None:
- if xml_components == AllComponents():
- for ical_subcomponent in component.subcomponents():
- result.addComponent(ical_subcomponent)
- else:
- for xml_subcomponent in xml_components:
- for ical_subcomponent in component.subcomponents():
- if ical_subcomponent.name() == xml_subcomponent.type:
- result.addComponent(self.compFilter(xml_subcomponent, ical_subcomponent))
-
- if xml_properties is not None:
- if xml_properties == AllProperties():
- for ical_property in component.properties():
- result.addProperty(ical_property)
- else:
- for xml_property in xml_properties:
- name = xml_property.property_name
- for ical_property in component.properties(name):
- result.addProperty(ical_property)
-
- return result
-
- def expandRecurrence(self, calendar, timezone=None):
- """
- Expand the recurrence set into individual items.
- @param calendar: the L{Component} for the calendar to operate on.
- @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
- @return: the L{Component} for the result.
- """
- return calendar.expand(self.calendardata.recurrence_set.start, self.calendardata.recurrence_set.end, timezone)
-
- def limitRecurrence(self, calendar):
- """
- Limit the set of overridden instances returned to only those
- that are needed to describe the range of instances covered
- by the specified time range.
- @param calendar: the L{Component} for the calendar to operate on.
- @return: the L{Component} for the result.
- """
- raise NotImplementedError()
- return calendar
-
- def limitFreeBusy(self, calendar):
- """
- Limit the range of any FREEBUSY properties in the calendar, returning
- a new calendar if limits were applied, or the same one if no limits were applied.
- @param calendar: the L{Component} for the calendar to operate on.
- @return: the L{Component} for the result.
- """
-
- # First check for any VFREEBUSYs - can ignore limit if there are none
- if calendar.mainType() != "VFREEBUSY":
- return calendar
-
- # Create duplicate calendar and filter FREEBUSY properties
- calendar = calendar.duplicate()
- for component in calendar.subcomponents():
- if component.name() != "VFREEBUSY":
- continue
- for property in component.properties("FREEBUSY"):
- newvalue = []
- for period in property.value():
- clipped = clipPeriod(period, (self.calendardata.freebusy_set.start, self.calendardata.freebusy_set.end))
- if clipped:
- newvalue.append(clipped)
- if len(newvalue):
- property.setValue(newvalue)
- else:
- component.removeProperty(property)
- return calendar
-
- def merge(self, icalnew, icalold):
- """
- Calendar-data merging does not happen
- """
- raise NotImplementedError
Copied: CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/calendardata.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/calendardata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,172 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.caldavxml import LimitRecurrenceSet, Expand, AllComponents,\
+ AllProperties
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.dateops import clipPeriod
+from twistedcaldav.ical import Component
+
+__all__ = [
+ "CalendarDataFilter",
+]
+
+class CalendarDataFilter(CalendarFilter):
+ """
+ Filter using the CALDAV:calendar-data element specification
+ """
+
+ def __init__(self, calendardata, timezone=None):
+ """
+
+ @param calendardata: the XML element describing how to filter
+ @type calendardata: L{CalendarData}
+ @param timezone: the VTIMEZONE to use for floating/all-day
+ @type timezone: L{Component}
+ """
+
+ self.calendardata = calendardata
+ self.timezone = timezone
+
+ def filter(self, ical):
+ """
+ Filter the supplied iCalendar (vobject) data using the request information.
+
+ @param ical: iCalendar object
+ @type ical: L{Component} or C{str}
+
+ @return: L{Component} for the filtered calendar data
+ """
+
+ # Empty element: get all data
+ if not self.calendardata.children:
+ return ical
+
+ # Make sure input is valid
+ ical = self.validCalendar(ical)
+
+ # Pre-process the calendar data based on expand and limit options
+ if self.calendardata.freebusy_set:
+ ical = self.limitFreeBusy(ical)
+
+ # Filter data based on any provided CALDAV:comp element, or use all current data
+ if self.calendardata.component is not None:
+ ical = self.compFilter(self.calendardata.component, ical)
+
+ # Post-process the calendar data based on the expand and limit options
+ if self.calendardata.recurrence_set:
+ if isinstance(self.calendardata.recurrence_set, LimitRecurrenceSet):
+ ical = self.limitRecurrence(ical)
+ elif isinstance(self.calendardata.recurrence_set, Expand):
+ ical = self.expandRecurrence(ical, self.timezone)
+
+ return ical
+
+ def compFilter(self, comp, component):
+ """
+ Returns a calendar component object containing the data in the given
+ component which is specified by this CalendarComponent.
+ """
+ if comp.type != component.name():
+ raise ValueError("%s of type %r can't get data from component of type %r"
+ % (comp.sname(), comp.type, component.name()))
+
+ result = Component(comp.type)
+
+ xml_components = comp.components
+ xml_properties = comp.properties
+
+ # Empty element means do all properties and components
+ if xml_components is None and xml_properties is None:
+ xml_components = AllComponents()
+ xml_properties = AllProperties()
+
+ if xml_components is not None:
+ if xml_components == AllComponents():
+ for ical_subcomponent in component.subcomponents():
+ result.addComponent(ical_subcomponent)
+ else:
+ for xml_subcomponent in xml_components:
+ for ical_subcomponent in component.subcomponents():
+ if ical_subcomponent.name() == xml_subcomponent.type:
+ result.addComponent(self.compFilter(xml_subcomponent, ical_subcomponent))
+
+ if xml_properties is not None:
+ if xml_properties == AllProperties():
+ for ical_property in component.properties():
+ result.addProperty(ical_property)
+ else:
+ for xml_property in xml_properties:
+ name = xml_property.property_name
+ for ical_property in component.properties(name):
+ result.addProperty(ical_property)
+
+ return result
+
+ def expandRecurrence(self, calendar, timezone=None):
+ """
+ Expand the recurrence set into individual items.
+ @param calendar: the L{Component} for the calendar to operate on.
+ @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
+ @return: the L{Component} for the result.
+ """
+ return calendar.expand(self.calendardata.recurrence_set.start, self.calendardata.recurrence_set.end, timezone)
+
+ def limitRecurrence(self, calendar):
+ """
+ Limit the set of overridden instances returned to only those
+ that are needed to describe the range of instances covered
+ by the specified time range.
+ @param calendar: the L{Component} for the calendar to operate on.
+ @return: the L{Component} for the result.
+ """
+ raise NotImplementedError()
+ return calendar
+
+ def limitFreeBusy(self, calendar):
+ """
+ Limit the range of any FREEBUSY properties in the calendar, returning
+ a new calendar if limits were applied, or the same one if no limits were applied.
+ @param calendar: the L{Component} for the calendar to operate on.
+ @return: the L{Component} for the result.
+ """
+
+ # First check for any VFREEBUSYs - can ignore limit if there are none
+ if calendar.mainType() != "VFREEBUSY":
+ return calendar
+
+ # Create duplicate calendar and filter FREEBUSY properties
+ calendar = calendar.duplicate()
+ for component in calendar.subcomponents():
+ if component.name() != "VFREEBUSY":
+ continue
+ for property in component.properties("FREEBUSY"):
+ newvalue = []
+ for period in property.value():
+ clipped = clipPeriod(period, (self.calendardata.freebusy_set.start, self.calendardata.freebusy_set.end))
+ if clipped:
+ newvalue.append(clipped)
+ if len(newvalue):
+ property.setValue(newvalue)
+ else:
+ component.removeProperty(property)
+ return calendar
+
+ def merge(self, icalnew, icalold):
+ """
+ Calendar-data merging does not happen
+ """
+ raise NotImplementedError
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/filter.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/filter.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,65 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-from twistedcaldav.ical import Component
-
-__all__ = [
- "CalendarFilter",
-]
-
-class CalendarFilter(object):
- """
- Abstract class that defines an iCalendar filter/merge object
- """
-
-
- def __init__(self):
- pass
-
- def filter(self, ical):
- """
- Filter the supplied iCalendar (vobject) data using the request information.
-
- @param ical: iCalendar object
- @type ical: L{Component}
-
- @return: L{Component} for the filtered calendar data
- """
- raise NotImplementedError
-
- def merge(self, icalnew, icalold):
- """
- Merge the old iCalendar (vobject) data into the new iCalendar data using the request information.
-
- @param icalnew: new iCalendar object to merge data into
- @type icalnew: L{Component}
- @param icalold: old iCalendar data to merge data from
- @type icalold: L{Component}
- """
- raise NotImplementedError
-
- def validCalendar(self, ical):
-
- # If we were passed a string, parse it out as a Component
- if isinstance(ical, str):
- try:
- ical = Component.fromString(ical)
- except ValueError:
- raise ValueError("Not a calendar: %r" % (ical,))
-
- if ical is None or ical.name() != "VCALENDAR":
- raise ValueError("Not a calendar: %r" % (ical,))
-
- return ical
Copied: CalendarServer/trunk/twistedcaldav/datafilters/filter.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/filter.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/filter.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/filter.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,65 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twistedcaldav.ical import Component
+
+__all__ = [
+ "CalendarFilter",
+]
+
+class CalendarFilter(object):
+ """
+ Abstract class that defines an iCalendar filter/merge object
+ """
+
+
+ def __init__(self):
+ pass
+
+ def filter(self, ical):
+ """
+ Filter the supplied iCalendar (vobject) data using the request information.
+
+ @param ical: iCalendar object
+ @type ical: L{Component}
+
+ @return: L{Component} for the filtered calendar data
+ """
+ raise NotImplementedError
+
+ def merge(self, icalnew, icalold):
+ """
+ Merge the old iCalendar (vobject) data into the new iCalendar data using the request information.
+
+ @param icalnew: new iCalendar object to merge data into
+ @type icalnew: L{Component}
+ @param icalold: old iCalendar data to merge data from
+ @type icalold: L{Component}
+ """
+ raise NotImplementedError
+
+ def validCalendar(self, ical):
+
+ # If we were passed a string, parse it out as a Component
+ if isinstance(ical, str):
+ try:
+ ical = Component.fromString(ical)
+ except ValueError:
+ raise ValueError("Not a calendar: %r" % (ical,))
+
+ if ical is None or ical.name() != "VCALENDAR":
+ raise ValueError("Not a calendar: %r" % (ical,))
+
+ return ical
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,336 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.datafilters.filter import CalendarFilter
-from twistedcaldav.ical import Component, Property
-
-__all__ = [
- "PerUserDataFilter",
-]
-
-"""
-Object model for calendar data is as follows:
-
-VCALENDAR
- VTIMEZONE*
- VEVENT* / VTODO* / VJOURNAL*
- BEGIN:X-CALENDARSERVER-PERUSER*
- X-CALENDARSERVER-PERUSER-UID
- UID
- BEGIN:X-CALENDARSERVER-PERINSTANCE
- RECURRENCE-ID?
- TRANSP?
- VALARM*
-
-So we will store per user data inside the top-level component (alongside VEVENT, VTODO etc). That new component will
-contain properties to identify the user and the UID of the VEVENT, VTODO it affects. It will contain sub-components
-for each instance overridden by the per-user data. These per-user overridden components may not correspond to an
-actual overridden component. In that situation the server has to re-construct the per-user data appropriately:
-
-e.g.,
-
-1. VEVENT contains an overridden instance, but X-CALENDARSERVER-PERUSER does not - server uses the must instance
-X-CALENDARSERVER-PERUSER data (if any) for the overridden instance.
-
-2. VEVENT does not contain an overridden instance, but X-CALENDARSERVER-PERUSER does - server synthesizes an
-overridden instance to match the X-CALENDARSERVER-PERUSER one.
-
-3. VEVENT contains overridden instance and X-CALENDARSERVER-PERUSER does - server merges X-CALENDARSERVER-PERUSER
-data into overridden instance.
-
-"""
-
-class PerUserDataFilter(CalendarFilter):
- """
- Filter per-user data
- """
-
- # If any of these change also change the vobject behaviors in this module's __init__.py
- # and update usage in ical.py
- PERUSER_COMPONENT = "X-CALENDARSERVER-PERUSER"
- PERUSER_UID = "X-CALENDARSERVER-PERUSER-UID"
- PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
-
- PERUSER_PROPERTIES = ("TRANSP",)
- PERUSER_SUBCOMPONENTS = ("VALARM",)
-
- def __init__(self, uid):
- """
-
- @param uid: unique identifier of the user for whom the data is being filtered
- @type uid: C{str}
- """
-
- self.uid = uid
-
- def filter(self, ical):
- """
- Filter the supplied iCalendar (vobject) data using the request information.
- Assume that the object is a CalDAV calendar resource.
-
- @param ical: iCalendar object - this will be modified and returned
- @type ical: L{Component} or C{str}
-
- @return: L{Component} for the filtered calendar data
- """
-
- # Make sure input is valid
- ical = self.validCalendar(ical)
-
- # Look for matching per-user sub-component, removing all the others
- peruser_component = None
- for component in tuple(ical.subcomponents()):
- if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
-
- # Check user id - remove if not matches
- if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
- ical.removeComponent(component)
- elif peruser_component is None:
- peruser_component = component
- ical.removeComponent(component)
- else:
- raise AssertionError("Can't have two X-CALENDARSERVER-PERUSER components for the same user")
-
- # Now transfer any components over
- if peruser_component:
- self._mergeBack(ical, peruser_component)
-
- return ical
-
- def merge(self, icalnew, icalold):
- """
- Merge the new data with the old taking per-user information into account.
-
- @param icalnew: new calendar data
- @type icalnew: L{Component} or C{str}
- @param icalold: existing calendar data
- @type icalold: L{Component} or C{str}
-
- @return: L{Component} for the merged calendar data
- """
-
- # Make sure input is valid
- icalnew = self.validCalendar(icalnew)
-
- # First split the new data into common and per-user pieces
- self._splitPerUserData(icalnew)
- if icalold is None:
- return icalnew
-
- # Make sure input is valid
- icalold = self.validCalendar(icalold)
-
- self._mergeRepresentations(icalnew, icalold)
- return icalnew
-
- def _mergeBack(self, ical, peruser):
- """
- Merge the per-user data back into the main calendar data.
-
- @param ical: main calendar data to merge into
- @type ical: L{Component}
- @param peruser: the per-user data to merge in
- @type peruser: L{Component}
- """
-
- # Iterate over each instance in the per-user data and build mapping
- peruser_recurrence_map = {}
- for subcomponent in peruser.subcomponents():
- if subcomponent.name() != PerUserDataFilter.PERINSTANCE_COMPONENT:
- raise AssertionError("Wrong sub-component '%s' in a X-CALENDARSERVER-PERUSER component" % (subcomponent.name(),))
- peruser_recurrence_map[subcomponent.getRecurrenceIDUTC()] = subcomponent
-
- ical_recurrence_set = set(ical.getComponentInstances())
- peruser_recurrence_set = set(peruser_recurrence_map.keys())
-
- # Set operations to find union and differences
- union_set = ical_recurrence_set.intersection(peruser_recurrence_set)
- ical_only_set = ical_recurrence_set.difference(peruser_recurrence_set)
- peruser_only_set = peruser_recurrence_set.difference(ical_recurrence_set)
-
- # For ones in per-user data but no main data, we synthesize an instance and copy over per-user data
- # NB We have to do this before we do any merge that may change the master
- if ical.masterComponent() is not None:
- for rid in peruser_only_set:
- ical_component = ical.deriveInstance(rid)
- peruser_component = peruser_recurrence_map[rid]
- self._mergeBackComponent(ical_component, peruser_component)
- ical.addComponent(ical_component)
- elif peruser_only_set:
- raise AssertionError("Cannot derive a per-user instance when there is no master component.")
-
- # Process the unions by merging in per-user data
- for rid in union_set:
- ical_component = ical.overriddenComponent(rid)
- peruser_component = peruser_recurrence_map[rid]
- self._mergeBackComponent(ical_component, peruser_component)
-
- # For ones in main data but no per-user data, we try and copy over the master per-user data
- if ical_only_set:
- peruser_master = peruser_recurrence_map.get(None)
- if peruser_master:
- for rid in ical_only_set:
- ical_component = ical.overriddenComponent(rid)
- self._mergeBackComponent(ical_component, peruser_master)
-
- def _mergeBackComponent(self, ical, peruser):
- """
- Copy all properties and sub-components from per-user data into the main component
- @param ical:
- @type ical:
- @param peruser:
- @type peruser:
- """
-
- # Each sub-component
- for subcomponent in peruser.subcomponents():
- ical.addComponent(subcomponent)
-
- # Each property except RECURRENCE-ID
- for property in peruser.properties():
- if property.name() == "RECURRENCE-ID":
- continue
- ical.addProperty(property)
-
- def _splitPerUserData(self, ical):
-
- def init_peruser_component():
- peruser = Component(PerUserDataFilter.PERUSER_COMPONENT)
- peruser.addProperty(Property("UID", ical.resourceUID()))
- peruser.addProperty(Property(PerUserDataFilter.PERUSER_UID, self.uid))
- ical.addComponent(peruser)
- return peruser
-
- components = tuple(ical.subcomponents())
- peruser_component = init_peruser_component() if self.uid else None
- perinstance_components = {}
-
- for component in components:
- if component.name() == "VTIMEZONE":
- continue
-
- def init_perinstance_component():
- peruser = Component(PerUserDataFilter.PERINSTANCE_COMPONENT)
- rid = component.getRecurrenceIDUTC()
- if rid:
- peruser.addProperty(Property("RECURRENCE-ID", rid))
- perinstance_components[rid] = peruser
- return peruser
-
- perinstance_component = init_perinstance_component() if self.uid else None
-
- # Transfer per-user properties from main component to per-instance component
- for property in tuple(component.properties()):
- if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-"):
- if self.uid:
- perinstance_component.addProperty(property)
- component.removeProperty(property)
-
- # Transfer per-user components from main component to per-instance component
- for subcomponent in tuple(component.subcomponents()):
- if subcomponent.name() in PerUserDataFilter.PERUSER_SUBCOMPONENTS or subcomponent.name().startswith("X-"):
- if self.uid:
- perinstance_component.addComponent(subcomponent)
- component.removeComponent(subcomponent)
-
- if self.uid:
- # Add unique per-instance components into the per-user component
- master_perinstance = perinstance_components.get(None)
- master_perinstance_txt = str(master_perinstance)
- if master_perinstance:
- peruser_component.addComponent(master_perinstance)
- for rid, perinstance in perinstance_components.iteritems():
- if rid is None:
- continue
- perinstance_txt = str(perinstance)
- perinstance_txt = "".join([line for line in perinstance_txt.splitlines(True) if not line.startswith("RECURRENCE-ID:")])
- if master_perinstance is None or perinstance_txt != master_perinstance_txt:
- peruser_component.addComponent(perinstance)
-
- self._compactInstances(ical)
-
- def _compactInstances(self, ical):
- """
- Remove recurrences instances that are the same as their master-derived counterparts. This gives the most
- compact representation of the calendar data.
-
- @param ical: calendar data to process
- @type ical: L{Component}
- """
-
- # Must have a master component in order to do this
- master = ical.masterComponent()
- if master is None:
- return
-
- for subcomponent in tuple(ical.subcomponents()):
- if subcomponent.name() == "VTIMEZONE" or subcomponent.name().startswith("X-"):
- continue
- rid = subcomponent.getRecurrenceIDUTC()
- if rid is None:
- continue
- derived = ical.deriveInstance(rid)
- if derived:
- if str(derived) == str(subcomponent):
- ical.removeComponent(subcomponent)
-
- def _mergeRepresentations(self, icalnew, icalold):
-
- # Test for simple case first
- if icalnew.isRecurring() and icalold.isRecurring():
- # Test each instance from old data to see whether it is still valid in the new one
- self._complexMerge(icalnew, icalold)
- else:
- self._simpleMerge(icalnew, icalold)
-
- def _simpleMerge(self, icalnew, icalold):
-
- # Take all per-user components from old and add to new, except for our user
- new_recur = icalnew.isRecurring()
- old_recur = icalold.isRecurring()
- new_recur_has_no_master = new_recur and (icalnew.masterComponent() is None)
- for component in icalold.subcomponents():
- if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
- if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid and not new_recur_has_no_master:
- newcomponent = component.duplicate()
-
- # Only transfer the master components from the old data to the new when the old
- # was recurring and the new is not recurring
- if not new_recur and old_recur:
- for subcomponent in tuple(newcomponent.subcomponents()):
- if subcomponent.getRecurrenceIDUTC() is not None:
- newcomponent.removeComponent(subcomponent)
-
- if len(tuple(newcomponent.subcomponents())):
- icalnew.addComponent(newcomponent)
-
- def _complexMerge(self, icalnew, icalold):
-
- # Take all per-user components from old and add to new, except for our user
- for component in icalold.subcomponents():
- if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
- if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
- newcomponent = component.duplicate()
-
- # See which of the instances are still valid
- old_rids = dict([(subcomponent.getRecurrenceIDUTC(), subcomponent,) for subcomponent in newcomponent.subcomponents()])
- valid_rids = icalnew.validInstances(old_rids.keys())
- for old_rid, subcomponent in old_rids.iteritems():
- if old_rid not in valid_rids:
- newcomponent.removeComponent(subcomponent)
-
- if len(tuple(newcomponent.subcomponents())):
- icalnew.addComponent(newcomponent)
Copied: CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,336 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.ical import Component, Property
+
+__all__ = [
+ "PerUserDataFilter",
+]
+
+"""
+Object model for calendar data is as follows:
+
+VCALENDAR
+ VTIMEZONE*
+ VEVENT* / VTODO* / VJOURNAL*
+ BEGIN:X-CALENDARSERVER-PERUSER*
+ X-CALENDARSERVER-PERUSER-UID
+ UID
+ BEGIN:X-CALENDARSERVER-PERINSTANCE
+ RECURRENCE-ID?
+ TRANSP?
+ VALARM*
+
+So we will store per user data inside the top-level component (alongside VEVENT, VTODO etc). That new component will
+contain properties to identify the user and the UID of the VEVENT, VTODO it affects. It will contain sub-components
+for each instance overridden by the per-user data. These per-user overridden components may not correspond to an
+actual overridden component. In that situation the server has to re-construct the per-user data appropriately:
+
+e.g.,
+
+1. VEVENT contains an overridden instance, but X-CALENDARSERVER-PERUSER does not - server uses the must instance
+X-CALENDARSERVER-PERUSER data (if any) for the overridden instance.
+
+2. VEVENT does not contain an overridden instance, but X-CALENDARSERVER-PERUSER does - server synthesizes an
+overridden instance to match the X-CALENDARSERVER-PERUSER one.
+
+3. VEVENT contains overridden instance and X-CALENDARSERVER-PERUSER does - server merges X-CALENDARSERVER-PERUSER
+data into overridden instance.
+
+"""
+
+class PerUserDataFilter(CalendarFilter):
+ """
+ Filter per-user data
+ """
+
+ # If any of these change also change the vobject behaviors in this module's __init__.py
+ # and update usage in ical.py
+ PERUSER_COMPONENT = "X-CALENDARSERVER-PERUSER"
+ PERUSER_UID = "X-CALENDARSERVER-PERUSER-UID"
+ PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
+
+ PERUSER_PROPERTIES = ("TRANSP",)
+ PERUSER_SUBCOMPONENTS = ("VALARM",)
+
+ def __init__(self, uid):
+ """
+
+ @param uid: unique identifier of the user for whom the data is being filtered
+ @type uid: C{str}
+ """
+
+ self.uid = uid
+
+ def filter(self, ical):
+ """
+ Filter the supplied iCalendar (vobject) data using the request information.
+ Assume that the object is a CalDAV calendar resource.
+
+ @param ical: iCalendar object - this will be modified and returned
+ @type ical: L{Component} or C{str}
+
+ @return: L{Component} for the filtered calendar data
+ """
+
+ # Make sure input is valid
+ ical = self.validCalendar(ical)
+
+ # Look for matching per-user sub-component, removing all the others
+ peruser_component = None
+ for component in tuple(ical.subcomponents()):
+ if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+
+ # Check user id - remove if not matches
+ if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
+ ical.removeComponent(component)
+ elif peruser_component is None:
+ peruser_component = component
+ ical.removeComponent(component)
+ else:
+ raise AssertionError("Can't have two X-CALENDARSERVER-PERUSER components for the same user")
+
+ # Now transfer any components over
+ if peruser_component:
+ self._mergeBack(ical, peruser_component)
+
+ return ical
+
+ def merge(self, icalnew, icalold):
+ """
+ Merge the new data with the old taking per-user information into account.
+
+ @param icalnew: new calendar data
+ @type icalnew: L{Component} or C{str}
+ @param icalold: existing calendar data
+ @type icalold: L{Component} or C{str}
+
+ @return: L{Component} for the merged calendar data
+ """
+
+ # Make sure input is valid
+ icalnew = self.validCalendar(icalnew)
+
+ # First split the new data into common and per-user pieces
+ self._splitPerUserData(icalnew)
+ if icalold is None:
+ return icalnew
+
+ # Make sure input is valid
+ icalold = self.validCalendar(icalold)
+
+ self._mergeRepresentations(icalnew, icalold)
+ return icalnew
+
+ def _mergeBack(self, ical, peruser):
+ """
+ Merge the per-user data back into the main calendar data.
+
+ @param ical: main calendar data to merge into
+ @type ical: L{Component}
+ @param peruser: the per-user data to merge in
+ @type peruser: L{Component}
+ """
+
+ # Iterate over each instance in the per-user data and build mapping
+ peruser_recurrence_map = {}
+ for subcomponent in peruser.subcomponents():
+ if subcomponent.name() != PerUserDataFilter.PERINSTANCE_COMPONENT:
+ raise AssertionError("Wrong sub-component '%s' in a X-CALENDARSERVER-PERUSER component" % (subcomponent.name(),))
+ peruser_recurrence_map[subcomponent.getRecurrenceIDUTC()] = subcomponent
+
+ ical_recurrence_set = set(ical.getComponentInstances())
+ peruser_recurrence_set = set(peruser_recurrence_map.keys())
+
+ # Set operations to find union and differences
+ union_set = ical_recurrence_set.intersection(peruser_recurrence_set)
+ ical_only_set = ical_recurrence_set.difference(peruser_recurrence_set)
+ peruser_only_set = peruser_recurrence_set.difference(ical_recurrence_set)
+
+ # For ones in per-user data but no main data, we synthesize an instance and copy over per-user data
+ # NB We have to do this before we do any merge that may change the master
+ if ical.masterComponent() is not None:
+ for rid in peruser_only_set:
+ ical_component = ical.deriveInstance(rid)
+ peruser_component = peruser_recurrence_map[rid]
+ self._mergeBackComponent(ical_component, peruser_component)
+ ical.addComponent(ical_component)
+ elif peruser_only_set:
+ raise AssertionError("Cannot derive a per-user instance when there is no master component.")
+
+ # Process the unions by merging in per-user data
+ for rid in union_set:
+ ical_component = ical.overriddenComponent(rid)
+ peruser_component = peruser_recurrence_map[rid]
+ self._mergeBackComponent(ical_component, peruser_component)
+
+ # For ones in main data but no per-user data, we try and copy over the master per-user data
+ if ical_only_set:
+ peruser_master = peruser_recurrence_map.get(None)
+ if peruser_master:
+ for rid in ical_only_set:
+ ical_component = ical.overriddenComponent(rid)
+ self._mergeBackComponent(ical_component, peruser_master)
+
+ def _mergeBackComponent(self, ical, peruser):
+ """
+ Copy all properties and sub-components from per-user data into the main component
+ @param ical:
+ @type ical:
+ @param peruser:
+ @type peruser:
+ """
+
+ # Each sub-component
+ for subcomponent in peruser.subcomponents():
+ ical.addComponent(subcomponent)
+
+ # Each property except RECURRENCE-ID
+ for property in peruser.properties():
+ if property.name() == "RECURRENCE-ID":
+ continue
+ ical.addProperty(property)
+
+ def _splitPerUserData(self, ical):
+
+ def init_peruser_component():
+ peruser = Component(PerUserDataFilter.PERUSER_COMPONENT)
+ peruser.addProperty(Property("UID", ical.resourceUID()))
+ peruser.addProperty(Property(PerUserDataFilter.PERUSER_UID, self.uid))
+ ical.addComponent(peruser)
+ return peruser
+
+ components = tuple(ical.subcomponents())
+ peruser_component = init_peruser_component() if self.uid else None
+ perinstance_components = {}
+
+ for component in components:
+ if component.name() == "VTIMEZONE":
+ continue
+
+ def init_perinstance_component():
+ peruser = Component(PerUserDataFilter.PERINSTANCE_COMPONENT)
+ rid = component.getRecurrenceIDUTC()
+ if rid:
+ peruser.addProperty(Property("RECURRENCE-ID", rid))
+ perinstance_components[rid] = peruser
+ return peruser
+
+ perinstance_component = init_perinstance_component() if self.uid else None
+
+ # Transfer per-user properties from main component to per-instance component
+ for property in tuple(component.properties()):
+ if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-"):
+ if self.uid:
+ perinstance_component.addProperty(property)
+ component.removeProperty(property)
+
+ # Transfer per-user components from main component to per-instance component
+ for subcomponent in tuple(component.subcomponents()):
+ if subcomponent.name() in PerUserDataFilter.PERUSER_SUBCOMPONENTS or subcomponent.name().startswith("X-"):
+ if self.uid:
+ perinstance_component.addComponent(subcomponent)
+ component.removeComponent(subcomponent)
+
+ if self.uid:
+ # Add unique per-instance components into the per-user component
+ master_perinstance = perinstance_components.get(None)
+ master_perinstance_txt = str(master_perinstance)
+ if master_perinstance:
+ peruser_component.addComponent(master_perinstance)
+ for rid, perinstance in perinstance_components.iteritems():
+ if rid is None:
+ continue
+ perinstance_txt = str(perinstance)
+ perinstance_txt = "".join([line for line in perinstance_txt.splitlines(True) if not line.startswith("RECURRENCE-ID:")])
+ if master_perinstance is None or perinstance_txt != master_perinstance_txt:
+ peruser_component.addComponent(perinstance)
+
+ self._compactInstances(ical)
+
+ def _compactInstances(self, ical):
+ """
+ Remove recurrences instances that are the same as their master-derived counterparts. This gives the most
+ compact representation of the calendar data.
+
+ @param ical: calendar data to process
+ @type ical: L{Component}
+ """
+
+ # Must have a master component in order to do this
+ master = ical.masterComponent()
+ if master is None:
+ return
+
+ for subcomponent in tuple(ical.subcomponents()):
+ if subcomponent.name() == "VTIMEZONE" or subcomponent.name().startswith("X-"):
+ continue
+ rid = subcomponent.getRecurrenceIDUTC()
+ if rid is None:
+ continue
+ derived = ical.deriveInstance(rid)
+ if derived:
+ if str(derived) == str(subcomponent):
+ ical.removeComponent(subcomponent)
+
+ def _mergeRepresentations(self, icalnew, icalold):
+
+ # Test for simple case first
+ if icalnew.isRecurring() and icalold.isRecurring():
+ # Test each instance from old data to see whether it is still valid in the new one
+ self._complexMerge(icalnew, icalold)
+ else:
+ self._simpleMerge(icalnew, icalold)
+
+ def _simpleMerge(self, icalnew, icalold):
+
+ # Take all per-user components from old and add to new, except for our user
+ new_recur = icalnew.isRecurring()
+ old_recur = icalold.isRecurring()
+ new_recur_has_no_master = new_recur and (icalnew.masterComponent() is None)
+ for component in icalold.subcomponents():
+ if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+ if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid and not new_recur_has_no_master:
+ newcomponent = component.duplicate()
+
+ # Only transfer the master components from the old data to the new when the old
+ # was recurring and the new is not recurring
+ if not new_recur and old_recur:
+ for subcomponent in tuple(newcomponent.subcomponents()):
+ if subcomponent.getRecurrenceIDUTC() is not None:
+ newcomponent.removeComponent(subcomponent)
+
+ if len(tuple(newcomponent.subcomponents())):
+ icalnew.addComponent(newcomponent)
+
+ def _complexMerge(self, icalnew, icalold):
+
+ # Take all per-user components from old and add to new, except for our user
+ for component in icalold.subcomponents():
+ if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+ if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
+ newcomponent = component.duplicate()
+
+ # See which of the instances are still valid
+ old_rids = dict([(subcomponent.getRecurrenceIDUTC(), subcomponent,) for subcomponent in newcomponent.subcomponents()])
+ valid_rids = icalnew.validInstances(old_rids.keys())
+ for old_rid, subcomponent in old_rids.iteritems():
+ if old_rid not in valid_rids:
+ newcomponent.removeComponent(subcomponent)
+
+ if len(tuple(newcomponent.subcomponents())):
+ icalnew.addComponent(newcomponent)
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/privateevents.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,173 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, StatusResponse
-from twistedcaldav.caldavxml import Property, CalendarData, CalendarComponent,\
- AllProperties, AllComponents
-from twistedcaldav.datafilters.calendardata import CalendarDataFilter
-from twistedcaldav.datafilters.filter import CalendarFilter
-from twistedcaldav.ical import Component
-
-__all__ = [
- "PrivateEventFilter",
-]
-
-class PrivateEventFilter(CalendarFilter):
- """
- Filter a private event to match the rights of the non-owner user accessing the data
- """
-
- def __init__(self, accessRestriction, isowner):
- """
-
- @param accessRestriction: one of the access levels in L{Component}
- @type accessRestriction: C{str}
- @param isowner: whether the current user is the owner of the data
- @type isowner: C{bool}
- """
-
- self.accessRestriction = accessRestriction
- self.isowner = isowner
-
- def filter(self, ical):
- """
- Filter the supplied iCalendar (vobject) data using the request information.
-
- @param ical: iCalendar object
- @type ical: L{Component} or C{str}
-
- @return: L{Component} for the filtered calendar data
- """
-
- if self.isowner or self.accessRestriction == Component.ACCESS_PUBLIC or self.accessRestriction is None:
- # No need to filter for the owner or public event
- return ical
-
- elif self.accessRestriction == Component.ACCESS_PRIVATE:
- # We should never get here because ACCESS_PRIVATE is protected via an ACL
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Access Denied"))
-
- elif self.accessRestriction == Component.ACCESS_PUBLIC:
- return ical
- elif self.accessRestriction in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
- # Create a CALDAV:calendar-data element with the appropriate iCalendar Component/Property
- # filter in place for the access restriction in use
-
- extra_access = ()
- if self.accessRestriction == Component.ACCESS_RESTRICTED:
- extra_access = (
- Property(name="SUMMARY"),
- Property(name="LOCATION"),
- )
-
- calendardata = CalendarData(
- CalendarComponent(
-
- # VCALENDAR properties
- Property(name="PRODID"),
- Property(name="VERSION"),
- Property(name="CALSCALE"),
- Property(name=Component.ACCESS_PROPERTY),
-
- # VEVENT
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="TRANSP"),
- Property(name="DTSTART"),
- Property(name="DTEND"),
- Property(name="DURATION"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VEVENT"}
- ),
-
- # VTODO
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="DTSTART"),
- Property(name="COMPLETED"),
- Property(name="DUE"),
- Property(name="DURATION"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VTODO"}
- ),
-
- # VJOURNAL
- CalendarComponent(
- Property(name="UID"),
- Property(name="RECURRENCE-ID"),
- Property(name="SEQUENCE"),
- Property(name="DTSTAMP"),
- Property(name="STATUS"),
- Property(name="TRANSP"),
- Property(name="DTSTART"),
- Property(name="RRULE"),
- Property(name="RDATE"),
- Property(name="EXRULE"),
- Property(name="EXDATE"),
- *extra_access,
- **{"name":"VJOURNAL"}
- ),
-
- # VFREEBUSY
- CalendarComponent(
- Property(name="UID"),
- Property(name="DTSTAMP"),
- Property(name="DTSTART"),
- Property(name="DTEND"),
- Property(name="DURATION"),
- Property(name="FREEBUSY"),
- *extra_access,
- **{"name":"VFREEBUSY"}
- ),
-
- # VTIMEZONE
- CalendarComponent(
- AllProperties(),
- AllComponents(),
- name="VTIMEZONE",
- ),
- name="VCALENDAR",
- ),
- )
-
- # Now "filter" the resource calendar data through the CALDAV:calendar-data element
- return CalendarDataFilter(calendardata).filter(ical)
- else:
- # Unknown access restriction
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Access Denied"))
-
- def merge(self, icalnew, icalold):
- """
- Private event merging does not happen
- """
- raise NotImplementedError
Copied: CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/privateevents.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,173 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, StatusResponse
+from twistedcaldav.caldavxml import Property, CalendarData, CalendarComponent,\
+ AllProperties, AllComponents
+from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.ical import Component
+
+__all__ = [
+ "PrivateEventFilter",
+]
+
+class PrivateEventFilter(CalendarFilter):
+ """
+ Filter a private event to match the rights of the non-owner user accessing the data
+ """
+
+ def __init__(self, accessRestriction, isowner):
+ """
+
+ @param accessRestriction: one of the access levels in L{Component}
+ @type accessRestriction: C{str}
+ @param isowner: whether the current user is the owner of the data
+ @type isowner: C{bool}
+ """
+
+ self.accessRestriction = accessRestriction
+ self.isowner = isowner
+
+ def filter(self, ical):
+ """
+ Filter the supplied iCalendar (vobject) data using the request information.
+
+ @param ical: iCalendar object
+ @type ical: L{Component} or C{str}
+
+ @return: L{Component} for the filtered calendar data
+ """
+
+ if self.isowner or self.accessRestriction == Component.ACCESS_PUBLIC or self.accessRestriction is None:
+ # No need to filter for the owner or public event
+ return ical
+
+ elif self.accessRestriction == Component.ACCESS_PRIVATE:
+ # We should never get here because ACCESS_PRIVATE is protected via an ACL
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Access Denied"))
+
+ elif self.accessRestriction == Component.ACCESS_PUBLIC:
+ return ical
+ elif self.accessRestriction in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+ # Create a CALDAV:calendar-data element with the appropriate iCalendar Component/Property
+ # filter in place for the access restriction in use
+
+ extra_access = ()
+ if self.accessRestriction == Component.ACCESS_RESTRICTED:
+ extra_access = (
+ Property(name="SUMMARY"),
+ Property(name="LOCATION"),
+ )
+
+ calendardata = CalendarData(
+ CalendarComponent(
+
+ # VCALENDAR properties
+ Property(name="PRODID"),
+ Property(name="VERSION"),
+ Property(name="CALSCALE"),
+ Property(name=Component.ACCESS_PROPERTY),
+
+ # VEVENT
+ CalendarComponent(
+ Property(name="UID"),
+ Property(name="RECURRENCE-ID"),
+ Property(name="SEQUENCE"),
+ Property(name="DTSTAMP"),
+ Property(name="STATUS"),
+ Property(name="TRANSP"),
+ Property(name="DTSTART"),
+ Property(name="DTEND"),
+ Property(name="DURATION"),
+ Property(name="RRULE"),
+ Property(name="RDATE"),
+ Property(name="EXRULE"),
+ Property(name="EXDATE"),
+ *extra_access,
+ **{"name":"VEVENT"}
+ ),
+
+ # VTODO
+ CalendarComponent(
+ Property(name="UID"),
+ Property(name="RECURRENCE-ID"),
+ Property(name="SEQUENCE"),
+ Property(name="DTSTAMP"),
+ Property(name="STATUS"),
+ Property(name="DTSTART"),
+ Property(name="COMPLETED"),
+ Property(name="DUE"),
+ Property(name="DURATION"),
+ Property(name="RRULE"),
+ Property(name="RDATE"),
+ Property(name="EXRULE"),
+ Property(name="EXDATE"),
+ *extra_access,
+ **{"name":"VTODO"}
+ ),
+
+ # VJOURNAL
+ CalendarComponent(
+ Property(name="UID"),
+ Property(name="RECURRENCE-ID"),
+ Property(name="SEQUENCE"),
+ Property(name="DTSTAMP"),
+ Property(name="STATUS"),
+ Property(name="TRANSP"),
+ Property(name="DTSTART"),
+ Property(name="RRULE"),
+ Property(name="RDATE"),
+ Property(name="EXRULE"),
+ Property(name="EXDATE"),
+ *extra_access,
+ **{"name":"VJOURNAL"}
+ ),
+
+ # VFREEBUSY
+ CalendarComponent(
+ Property(name="UID"),
+ Property(name="DTSTAMP"),
+ Property(name="DTSTART"),
+ Property(name="DTEND"),
+ Property(name="DURATION"),
+ Property(name="FREEBUSY"),
+ *extra_access,
+ **{"name":"VFREEBUSY"}
+ ),
+
+ # VTIMEZONE
+ CalendarComponent(
+ AllProperties(),
+ AllComponents(),
+ name="VTIMEZONE",
+ ),
+ name="VCALENDAR",
+ ),
+ )
+
+ # Now "filter" the resource calendar data through the CALDAV:calendar-data element
+ return CalendarDataFilter(calendardata).filter(ical)
+ else:
+ # Unknown access restriction
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Access Denied"))
+
+ def merge(self, icalnew, icalold):
+ """
+ Private event merging does not happen
+ """
+ raise NotImplementedError
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/__init__.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,19 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Tests for the twistedcaldav.datafilters module.
-"""
Copied: CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for the twistedcaldav.datafilters module.
+"""
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_calendardata.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,371 +0,0 @@
-##
-# Copyright (c) 2009 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.
-##
-
-import twistedcaldav.test.util
-from twistedcaldav.datafilters.calendardata import CalendarDataFilter
-from twistedcaldav.caldavxml import CalendarData, CalendarComponent,\
- AllComponents, AllProperties, Property
-from twistedcaldav.ical import Component
-
-class CalendarDataTest (twistedcaldav.test.util.TestCase):
-
- def test_empty(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData()
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(empty).filter(item)), data)
-
- def test_vcalendar_no_effect(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- no_effect = CalendarData(
- CalendarComponent(
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
-
- no_effect = CalendarData(
- CalendarComponent(
- AllComponents(),
- AllProperties(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
-
- def test_vcalendar_no_props(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-X-WR-CALNAME:Help
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//PYVOBJECT//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData(
- CalendarComponent(
- AllComponents(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
-
- def test_vcalendar_no_comp(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-X-WR-CALNAME:Help
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-X-WR-CALNAME:Help
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData(
- CalendarComponent(
- AllProperties(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
-
- def test_vevent_no_effect(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- no_effect = CalendarData(
- CalendarComponent(
- CalendarComponent(
- name="VEVENT"
- ),
- AllProperties(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
-
- def test_vevent_other_component(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- other_component = CalendarData(
- CalendarComponent(
- CalendarComponent(
- name="VTODO"
- ),
- AllProperties(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(other_component).filter(item)), result)
-
- def test_vevent_no_props(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData(
- CalendarComponent(
- CalendarComponent(
- AllComponents(),
- name="VEVENT"
- ),
- AllProperties(),
- name="VCALENDAR"
- )
- )
-
- for item in (data, Component.fromString(data),):
- filtered = str(CalendarDataFilter(empty).filter(item))
- filtered = "".join([line for line in filtered.splitlines(True) if not line.startswith("UID:")])
- self.assertEqual(filtered, result)
-
- def test_vevent_no_comp(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData(
- CalendarComponent(
- CalendarComponent(
- AllProperties(),
- name="VEVENT"
- ),
- AllProperties(),
- name="VCALENDAR"
- )
- )
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
-
- def test_vevent_some_props(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- result = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- empty = CalendarData(
- CalendarComponent(
- CalendarComponent(
- AllComponents(),
- Property(
- name="UID",
- ),
- Property(
- name="DTSTART",
- ),
- Property(
- name="DTEND",
- ),
- name="VEVENT"
- ),
- AllProperties(),
- name="VCALENDAR"
- )
- )
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
-
Copied: CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_calendardata.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_calendardata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,371 @@
+##
+# Copyright (c) 2009 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.
+##
+
+import twistedcaldav.test.util
+from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.caldavxml import CalendarData, CalendarComponent,\
+ AllComponents, AllProperties, Property
+from twistedcaldav.ical import Component
+
+class CalendarDataTest (twistedcaldav.test.util.TestCase):
+
+ def test_empty(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData()
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(empty).filter(item)), data)
+
+ def test_vcalendar_no_effect(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ no_effect = CalendarData(
+ CalendarComponent(
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
+
+ no_effect = CalendarData(
+ CalendarComponent(
+ AllComponents(),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
+
+ def test_vcalendar_no_props(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-WR-CALNAME:Help
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData(
+ CalendarComponent(
+ AllComponents(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
+
+ def test_vcalendar_no_comp(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-WR-CALNAME:Help
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-WR-CALNAME:Help
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData(
+ CalendarComponent(
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
+
+ def test_vevent_no_effect(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ no_effect = CalendarData(
+ CalendarComponent(
+ CalendarComponent(
+ name="VEVENT"
+ ),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
+
+ def test_vevent_other_component(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ other_component = CalendarData(
+ CalendarComponent(
+ CalendarComponent(
+ name="VTODO"
+ ),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(other_component).filter(item)), result)
+
+ def test_vevent_no_props(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData(
+ CalendarComponent(
+ CalendarComponent(
+ AllComponents(),
+ name="VEVENT"
+ ),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+
+ for item in (data, Component.fromString(data),):
+ filtered = str(CalendarDataFilter(empty).filter(item))
+ filtered = "".join([line for line in filtered.splitlines(True) if not line.startswith("UID:")])
+ self.assertEqual(filtered, result)
+
+ def test_vevent_no_comp(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData(
+ CalendarComponent(
+ CalendarComponent(
+ AllProperties(),
+ name="VEVENT"
+ ),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
+
+ def test_vevent_some_props(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ empty = CalendarData(
+ CalendarComponent(
+ CalendarComponent(
+ AllComponents(),
+ Property(
+ name="UID",
+ ),
+ Property(
+ name="DTSTART",
+ ),
+ Property(
+ name="DTEND",
+ ),
+ name="VEVENT"
+ ),
+ AllProperties(),
+ name="VCALENDAR"
+ )
+ )
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(CalendarDataFilter(empty).filter(item)), result)
+
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,5688 +0,0 @@
-##
-# Copyright (c) 2009 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.
-##
-
-import twistedcaldav.test.util
-from twistedcaldav.ical import Component
-from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
-
-class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
-
- def test_public_oneuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
- def test_public_twousers(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test01
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test01
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result03 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
-
-class PerUserDataFilterTestRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
-
- def test_public_oneuser_master(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
- def test_public_oneuser_master_and_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
- def test_public_oneuser_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
- def test_public_oneuser_master_derived_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
- def test_public_oneuser_master_derived_override_x2(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080603T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result03 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
-
- def test_public_oneuser_no_master_and_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
-
-class PerUserDataMergeTestNewNotRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
-
- def test_public_oneuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
-class PerUserDataMergeTestNewRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
-
- def test_public_oneuser_master(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
- def test_public_oneuser_master_and_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
- def test_public_oneuser_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
- def test_public_oneuser_master_compact_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
- def test_public_oneuser_master_noncompact_override(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-master
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-override
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
-
-class PerUserDataMergeTestExistingNotRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestExistingNowRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_noperuser_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_noperuser_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_oneuser_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_oneuser_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-
- def test_public_twousers_removal_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestExistingWasRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_noperuser_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_noperuser_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_oneuser_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_oneuser_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal_master(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal_master_with_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-
- def test_public_twousers_removal_only_override(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestBothRecurringMasterOnly (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_invalid_instance(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080701T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestBothRecurringMasterWithOverride (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.1
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestBothRecurringOverrideOnly (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- newresult = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
-
- def test_public_oneuser(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_removal(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T110000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1.2
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2.2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
-class PerUserDataMergeTestBothRecurringSpecialCase (twistedcaldav.test.util.TestCase):
-
- def test_public_twousers_recurrence_truncation(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=5
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080605T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080610T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=5
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080605T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_recurrence_shift(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080602T110000Z
-DTEND:20080602T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080610T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080602T110000Z
-DTEND:20080602T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080610T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_rdate_removed(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RDATE:20080602T150000Z
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T150000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
- def test_public_twousers_exdate_added(self):
-
- newdata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-EXDATE:20080602T110000Z
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- olddata = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1
-TRIGGER;RELATED=START:-PT20M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T110000Z
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T110000Z
-DTEND:20080601T120000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-EXDATE:20080602T110000Z
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-1mod
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-TRANSP:OPAQUE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test-2
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for olditem in (olddata, Component.fromString(olddata),):
- for newitem in (newdata, Component.fromString(newdata),):
- self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
-
Copied: CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,5688 @@
+##
+# Copyright (c) 2009 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.
+##
+
+import twistedcaldav.test.util
+from twistedcaldav.ical import Component
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+
+class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
+
+ def test_public_oneuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_twousers(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result03 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
+
+class PerUserDataFilterTestRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
+
+ def test_public_oneuser_master(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_oneuser_master_and_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_oneuser_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_oneuser_master_derived_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_oneuser_master_derived_override_x2(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T120000Z
+DTEND:20080603T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result03 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
+
+ def test_public_oneuser_no_master_and_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+class PerUserDataMergeTestNewNotRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
+
+ def test_public_oneuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+class PerUserDataMergeTestNewRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
+
+ def test_public_oneuser_master(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+ def test_public_oneuser_master_and_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+ def test_public_oneuser_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+ def test_public_oneuser_master_compact_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+ def test_public_oneuser_master_noncompact_override(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
+
+class PerUserDataMergeTestExistingNotRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestExistingNowRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_noperuser_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_noperuser_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_oneuser_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_oneuser_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+
+ def test_public_twousers_removal_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestExistingWasRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_noperuser_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_noperuser_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_oneuser_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_oneuser_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal_master(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal_master_with_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+
+ def test_public_twousers_removal_only_override(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestBothRecurringMasterOnly (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_invalid_instance(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080701T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestBothRecurringMasterWithOverride (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestBothRecurringOverrideOnly (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
+
+ def test_public_oneuser(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_removal(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1.2
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2.2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+class PerUserDataMergeTestBothRecurringSpecialCase (twistedcaldav.test.util.TestCase):
+
+ def test_public_twousers_recurrence_truncation(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080605T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080610T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080605T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_recurrence_shift(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T110000Z
+DTEND:20080602T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080610T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T110000Z
+DTEND:20080602T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080610T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_rdate_removed(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RDATE:20080602T150000Z
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T150000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
+ def test_public_twousers_exdate_added(self):
+
+ newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080602T110000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1
+TRIGGER;RELATED=START:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080602T110000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for olditem in (olddata, Component.fromString(olddata),):
+ for newitem in (newdata, Component.fromString(newdata),):
+ self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
+
Deleted: CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_privateevents.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,179 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.web2.http import HTTPError
-import twistedcaldav.test.util
-from twistedcaldav.datafilters.privateevents import PrivateEventFilter
-from twistedcaldav.ical import Component
-
-class PrivateEventsTest (twistedcaldav.test.util.TestCase):
-
- def test_public_default(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, True).filter(item)), data)
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, False).filter(item)), data)
-
- def test_public_none(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(None, True).filter(item)), data)
- self.assertEqual(str(PrivateEventFilter(None, False).filter(item)), data)
-
- def test_public(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-X-CALENDARSERVER-ACCESS:PUBLIC
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, True).filter(item)), data)
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, False).filter(item)), data)
-
- def test_private(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-X-CALENDARSERVER-ACCESS:PRIVATE
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PRIVATE, True).filter(item)), data)
- pfilter = PrivateEventFilter(Component.ACCESS_PRIVATE, False)
- self.assertRaises(HTTPError, pfilter.filter, item)
-
- def test_confidential(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-DESCRIPTION:In confidence
-LOCATION:My office
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-SUMMARY:Confidential
-END:VEVENT
-X-CALENDARSERVER-ACCESS:CONFIDENTIAL
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- filtered = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-END:VEVENT
-X-CALENDARSERVER-ACCESS:CONFIDENTIAL
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, True).filter(item)), data)
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, False).filter(item)), filtered)
-
- def test_restricted(self):
-
- data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-DESCRIPTION:In confidence
-LOCATION:My office
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-SUMMARY:Confidential
-END:VEVENT
-X-CALENDARSERVER-ACCESS:RESTRICTED
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- filtered = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-LOCATION:My office
-SUMMARY:Confidential
-END:VEVENT
-X-CALENDARSERVER-ACCESS:RESTRICTED
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_RESTRICTED, True).filter(item)), data)
- self.assertEqual(str(PrivateEventFilter(Component.ACCESS_RESTRICTED, False).filter(item)), filtered)
Copied: CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_privateevents.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_privateevents.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,179 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.web2.http import HTTPError
+import twistedcaldav.test.util
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.ical import Component
+
+class PrivateEventsTest (twistedcaldav.test.util.TestCase):
+
+ def test_public_default(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, True).filter(item)), data)
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, False).filter(item)), data)
+
+ def test_public_none(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(None, True).filter(item)), data)
+ self.assertEqual(str(PrivateEventFilter(None, False).filter(item)), data)
+
+ def test_public(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+X-CALENDARSERVER-ACCESS:PUBLIC
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, True).filter(item)), data)
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PUBLIC, False).filter(item)), data)
+
+ def test_private(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+X-CALENDARSERVER-ACCESS:PRIVATE
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_PRIVATE, True).filter(item)), data)
+ pfilter = PrivateEventFilter(Component.ACCESS_PRIVATE, False)
+ self.assertRaises(HTTPError, pfilter.filter, item)
+
+ def test_confidential(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DESCRIPTION:In confidence
+LOCATION:My office
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+SUMMARY:Confidential
+END:VEVENT
+X-CALENDARSERVER-ACCESS:CONFIDENTIAL
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ filtered = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+END:VEVENT
+X-CALENDARSERVER-ACCESS:CONFIDENTIAL
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, True).filter(item)), data)
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, False).filter(item)), filtered)
+
+ def test_restricted(self):
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DESCRIPTION:In confidence
+LOCATION:My office
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+SUMMARY:Confidential
+END:VEVENT
+X-CALENDARSERVER-ACCESS:RESTRICTED
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ filtered = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+LOCATION:My office
+SUMMARY:Confidential
+END:VEVENT
+X-CALENDARSERVER-ACCESS:RESTRICTED
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ for item in (data, Component.fromString(data),):
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_RESTRICTED, True).filter(item)), data)
+ self.assertEqual(str(PrivateEventFilter(Component.ACCESS_RESTRICTED, False).filter(item)), filtered)
Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -46,6 +46,7 @@
from twistedcaldav.directory.idirectory import IDirectoryService
from twistedcaldav.directory.wiki import getWikiACL
from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
+from twistedcaldav.notifications import NotificationCollectionResource
log = Logger()
@@ -277,6 +278,10 @@
childlist += (
("freebusy", FreeBusyURLResource),
)
+ if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
+ childlist += (
+ ("notification", NotificationCollectionResource),
+ )
for name, cls in childlist:
child = self.provisionChild(name)
assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)
@@ -360,6 +365,9 @@
def ownerPrincipal(self, request):
return succeed(self.principalForRecord())
+ def resourceOwnerPrincipal(self, request):
+ return succeed(self.principalForRecord())
+
def defaultAccessControlList(self):
myPrincipal = self.principalForRecord()
Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -128,13 +128,13 @@
"""
return ProxyDBService
- def resourceType(self):
+ def resourceType(self, request):
if self.proxyType == "calendar-proxy-read":
- return davxml.ResourceType.calendarproxyread
+ return succeed(davxml.ResourceType.calendarproxyread)
elif self.proxyType == "calendar-proxy-write":
- return davxml.ResourceType.calendarproxywrite
+ return succeed(davxml.ResourceType.calendarproxywrite)
else:
- return super(CalendarUserProxyPrincipalResource, self).resourceType()
+ return super(CalendarUserProxyPrincipalResource, self).resourceType(request)
def isProxyType(self, read_write):
if (
Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -124,12 +124,16 @@
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
raise UnauthorizedLogin("Incorrect credentials for %s" % (credentials.credentials.username,))
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -887,6 +887,16 @@
return succeed(inbox)
+ def notificationCollection(self, request):
+
+ notification = None
+ if config.Sharing.Enabled:
+ home = self.calendarHome()
+ if home is not None:
+ notification = home.getChild("notification")
+
+ return succeed(notification)
+
def calendarHomeURLs(self):
homeURL = self._homeChildURL(None)
return (homeURL,) if homeURL else ()
@@ -903,6 +913,12 @@
else:
return None
+ def notificationURL(self):
+ if config.Sharing.Enabled:
+ return self._homeChildURL("notification/")
+ else:
+ return None
+
def addressBookHomeURLs(self):
home = self._addressBookHome()
if home is None:
Modified: CalendarServer/trunk/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sudo.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directory/sudo.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -109,6 +109,8 @@
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
raise UnauthorizedLogin(
Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -94,8 +94,8 @@
),
)
- def resourceType(self):
- return davxml.ResourceType.addressbook #@UndefinedVariable
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.addressbook)
def isDirectoryBackedAddressBookCollection(self):
return True
Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -32,14 +32,16 @@
from twistedcaldav.customxml import calendarserver_namespace
+from twisted.internet.defer import succeed
+
log = Logger()
class DropBoxHomeResource (DAVResource):
"""
Drop box collection resource.
"""
- def resourceType(self):
- return davxml.ResourceType.dropboxhome
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.dropboxhome)
def isCollection(self):
return True
@@ -51,8 +53,8 @@
"""
Drop box resource.
"""
- def resourceType(self):
- return davxml.ResourceType.dropbox
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.dropbox)
def isCollection(self):
return True
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -472,7 +472,8 @@
if namespace == dav_namespace:
if name == "resourcetype":
- returnValue(self.resourceType())
+ rtype = (yield self.resourceType(request))
+ returnValue(rtype)
elif namespace == calendarserver_namespace:
if name == "expanded-group-member-set":
@@ -513,14 +514,14 @@
def expandedGroupMemberships(self):
return succeed(())
- def resourceType(self):
+ def resourceType(self, request):
# Allow live property to be overridden by dead property
if self.deadProperties().contains((dav_namespace, "resourcetype")):
- return self.deadProperties().get((dav_namespace, "resourcetype"))
+ return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
if self.isCollection():
- return davxml.ResourceType(davxml.Collection(), davxml.Principal())
+ return succeed(davxml.ResourceType(davxml.Collection(), davxml.Principal()))
else:
- return davxml.ResourceType(davxml.Principal())
+ return succeed(davxml.ResourceType(davxml.Principal()))
class DAVFile (SudoersMixin, SuperDAVFile, LoggingMixIn):
@@ -534,17 +535,17 @@
qname = property.qname()
if qname == (dav_namespace, "resourcetype"):
- return succeed(self.resourceType())
+ return self.resourceType(request)
return super(DAVFile, self).readProperty(property, request)
- def resourceType(self):
+ def resourceType(self, request):
# Allow live property to be overridden by dead property
if self.deadProperties().contains((dav_namespace, "resourcetype")):
- return self.deadProperties().get((dav_namespace, "resourcetype"))
+ return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
if self.isCollection():
- return davxml.ResourceType.collection
- return davxml.ResourceType.empty
+ return succeed(davxml.ResourceType.collection)
+ return succeed(davxml.ResourceType.empty)
def render(self, request):
if not self.fp.exists():
@@ -861,38 +862,44 @@
self.propertyStore = propertyStore
self.resource = propertyStore.resource
- def get(self, qname):
+ def get(self, qname, uid=None):
#self.log_debug("Get: %r, %r" % (self.resource.fp.path, qname))
cache = self._cache()
+
+ cachedQname = qname + (uid,)
- if qname in cache:
- property = cache.get(qname, None)
+ if cachedQname in cache:
+ property = cache.get(cachedQname, None)
if property is None:
self.log_debug("Cache miss: %r, %r, %r" % (self, self.resource.fp.path, qname))
try:
- property = self.propertyStore.get(qname)
+ property = self.propertyStore.get(qname, uid)
except HTTPError:
- del cache[qname]
+ del cache[cachedQname]
raise PropertyNotFoundError(qname)
- cache[qname] = property
+ cache[cachedQname] = property
return property
else:
raise PropertyNotFoundError(qname)
- def set(self, property):
+ def set(self, property, uid=None):
#self.log_debug("Set: %r, %r" % (self.resource.fp.path, property))
cache = self._cache()
- cache[property.qname()] = None
- self.propertyStore.set(property)
- cache[property.qname()] = property
+ cachedQname = property.qname() + (uid,)
- def contains(self, qname):
+ cache[cachedQname] = None
+ self.propertyStore.set(property, uid)
+ cache[cachedQname] = property
+
+ def contains(self, qname, uid=None):
#self.log_debug("Contains: %r, %r" % (self.resource.fp.path, qname))
+ cachedQname = qname + (uid,)
+
try:
cache = self._cache()
except HTTPError, e:
@@ -901,30 +908,40 @@
else:
raise
- if qname in cache:
+ if cachedQname in cache:
#self.log_debug("Contains cache hit: %r, %r, %r" % (self, self.resource.fp.path, qname))
return True
else:
return False
- def delete(self, qname):
+ def delete(self, qname, uid=None):
#self.log_debug("Delete: %r, %r" % (self.resource.fp.path, qname))
- if self._data is not None and qname in self._data:
- del self._data[qname]
+ cachedQname = qname + (uid,)
- self.propertyStore.delete(qname)
+ if self._data is not None and cachedQname in self._data:
+ del self._data[cachedQname]
- def list(self):
+ self.propertyStore.delete(qname, uid)
+
+ def list(self, uid=None, filterByUID=True):
#self.log_debug("List: %r" % (self.resource.fp.path,))
- return self._cache().iterkeys()
+ keys = self._cache().iterkeys()
+ if filterByUID:
+ return [
+ (namespace, name)
+ for namespace, name, propuid in keys
+ if propuid == uid
+ ]
+ else:
+ return keys
def _cache(self):
if not hasattr(self, "_data"):
#self.log_debug("Cache init: %r" % (self.resource.fp.path,))
self._data = dict(
(name, None)
- for name in self.propertyStore.list()
+ for name in self.propertyStore.list(filterByUID=False)
)
return self._data
Modified: CalendarServer/trunk/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/freebusyurl.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/freebusyurl.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -26,7 +26,7 @@
from vobject.icalendar import utc
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twext.python.log import Logger
from twext.web2 import responsecode
@@ -99,8 +99,8 @@
)
return davxml.ACL(*aces)
- def resourceType(self):
- return davxml.ResourceType.freebusyurl
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.freebusyurl)
def isCollection(self):
return False
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -131,6 +131,8 @@
"ORGANIZER": normalizeCUAddr,
}
+ignoredComponents = ("VTIMEZONE", "X-CALENDARSERVER-PERUSER",)
+
class InvalidICalendarDataError(ValueError):
pass
@@ -423,7 +425,7 @@
mtype = None
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
elif mtype and (mtype != component.name()):
raise InvalidICalendarDataError("Component contains more than one type of primary type: %r" % (self,))
@@ -442,7 +444,7 @@
result = None
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
elif not allow_multiple and (result is not None):
raise InvalidICalendarDataError("Calendar contains more than one primary component: %r" % (self,))
@@ -462,7 +464,7 @@
assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
if not component.hasProperty("RECURRENCE-ID"):
return component
@@ -481,7 +483,7 @@
assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
rid = component.getRecurrenceIDUTC()
if rid and recurrence_id and dateordatetime(rid) == recurrence_id:
@@ -758,10 +760,6 @@
if self.name() not in ("VEVENT", ):
return "FREE"
- # If it is TRANSPARENT we always ignore it
- if self.propertyValue("TRANSP") == "TRANSPARENT":
- return "FREE"
-
# Handle status
status = self.propertyValue("STATUS")
if status == "CANCELLED":
@@ -1023,7 +1021,7 @@
if self.name() == "VCALENDAR":
result = ()
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
result += component.getComponentInstances()
return result
else:
@@ -1038,7 +1036,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE" and component.isRecurring():
+ if component.name() not in ignoredComponents and component.isRecurring():
return True
else:
for propname in ("RRULE", "RDATE", "EXDATE", "RECURRENCE-ID",):
@@ -1158,6 +1156,51 @@
return newcomp
+ def validInstances(self, rids):
+ """
+ Test whether the specified recurrence-ids are valid instances in this event.
+
+ @param rid: recurrence-id values
+ @type rid: iterable
+
+ @return: C{set} of valid rids
+ """
+
+ valid = set()
+ non_master_rids = [rid for rid in rids if rid is not None]
+ if non_master_rids:
+ highest_rid = max(non_master_rids)
+ self.cacheExpandedTimeRanges(highest_rid + datetime.timedelta(days=1))
+ for rid in rids:
+ if self.validInstance(rid, clear_cache=False):
+ valid.add(rid)
+ return valid
+
+ def validInstance(self, rid, clear_cache=True):
+ """
+ Test whether the specified recurrence-id is a valid instance in this event.
+
+ @param rid: recurrence-id value
+ @type rid: L{datetime.datetime}
+
+ @return: C{bool}
+ """
+
+ # First check overridden instances already in this component
+ if not hasattr(self, "cachedComponentInstances") or clear_cache:
+ self.cachedComponentInstances = set(self.getComponentInstances())
+ if rid in self.cachedComponentInstances:
+ return True
+
+ # Must have a master component
+ if self.masterComponent() is None:
+ return False
+
+ # Get expansion
+ instances = self.cacheExpandedTimeRanges(rid + datetime.timedelta(days=1))
+ new_rids = set([instances[key].rid for key in instances])
+ return rid in new_rids
+
def resourceUID(self):
"""
@return: the UID of the subcomponents in this component.
@@ -1166,7 +1209,7 @@
if not hasattr(self, "_resource_uid"):
for subcomponent in self.subcomponents():
- if subcomponent.name() != "VTIMEZONE":
+ if subcomponent.name() not in ignoredComponents:
self._resource_uid = subcomponent.propertyValue("UID")
break
else:
@@ -1188,6 +1231,8 @@
name = subcomponent.name()
if name == "VTIMEZONE":
has_timezone = True
+ elif subcomponent.name() in ignoredComponents:
+ continue
else:
self._resource_type = name
break
@@ -1259,6 +1304,8 @@
if subcomponent.name() == "VTIMEZONE":
timezones.add(subcomponent.propertyValue("TZID"))
+ elif subcomponent.name() in ignoredComponents:
+ continue
else:
if ctype is None:
ctype = subcomponent.name()
@@ -1477,7 +1524,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
return component.getOrganizer()
else:
try:
@@ -1499,7 +1546,7 @@
if self.name() == "VCALENDAR":
result = ()
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
result += component.getOrganizersByInstance()
return result
else:
@@ -1523,7 +1570,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
return component.getOrganizerProperty()
else:
try:
@@ -1557,7 +1604,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
return component.getAttendees()
else:
# Find the property values
@@ -1580,7 +1627,7 @@
if self.name() == "VCALENDAR":
result = ()
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
result += component.getAttendeesByInstance(makeUnique, onlyScheduleAgentServer)
return result
else:
@@ -1618,7 +1665,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
attendee = component.getAttendeeProperty(match)
if attendee is not None:
return attendee
@@ -1643,7 +1690,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
results = []
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
attendee = component.getAttendeeProperty(match)
if attendee:
results.append(attendee)
@@ -1660,7 +1707,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
for attendee in component.getAllAttendeeProperties():
yield attendee
else:
@@ -1679,7 +1726,7 @@
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() != "VTIMEZONE":
+ if component.name() not in ignoredComponents:
return component.getMaskUID()
else:
try:
@@ -1705,7 +1752,7 @@
"""
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
for property in component.properties(propname):
if propvalue is None or property.value() == propvalue:
@@ -1738,7 +1785,7 @@
"""
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
found = component.getProperty(property.name())
if not found or found.value() != property.value():
@@ -1755,7 +1802,7 @@
"""
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.addProperty(property)
@@ -1766,7 +1813,7 @@
"""
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.replaceProperty(property)
@@ -1785,7 +1832,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.transferProperties(from_calendar, properties)
else:
@@ -1815,7 +1862,7 @@
master_component = None
removed_master = False
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
found_all_attendees = True
for attendee in attendees:
@@ -1859,7 +1906,7 @@
components = tuple(self.subcomponents())
remaining = len(components)
for component in components:
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
remaining -= 1
continue
rid = component.getRecurrenceIDUTC()
@@ -1877,7 +1924,7 @@
assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
[component.removeProperty(p) for p in tuple(component.properties("ATTENDEE")) if p.value().lower() != attendee.lower()]
@@ -1888,7 +1935,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.removeAlarms()
else:
@@ -1905,13 +1952,22 @@
for component in self.subcomponents():
component.filterProperties(remove, keep, do_subcomponents=False)
else:
- if self.name() == "VTIMEZONE":
+ if self.name() in ignoredComponents:
return
for p in tuple(self.properties()):
if (keep and p.name() not in keep) or (remove and p.name() in remove):
self.removeProperty(p)
+ def removeXComponents(self, keep_components=()):
+ """
+ Remove all X- properties except the specified ones
+ """
+
+ for component in tuple(self.subcomponents()):
+ if component.name().startswith("X-") and component.name() not in keep_components:
+ self.removeComponent(component)
+
def removeXProperties(self, keep_properties=(), remove_x_parameters=True, do_subcomponents=True):
"""
Remove all X- properties except the specified ones
@@ -1921,7 +1977,7 @@
for component in self.subcomponents():
component.removeXProperties(keep_properties, remove_x_parameters, do_subcomponents=False)
else:
- if self.name() == "VTIMEZONE":
+ if self.name() in ignoredComponents:
return
for p in tuple(self.properties()):
xpname = p.name().startswith("X-")
@@ -1939,7 +1995,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.removePropertyParameters(property, params)
else:
@@ -1958,7 +2014,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.removePropertyParametersByValue(property, paramvalues)
else:
@@ -2079,7 +2135,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.normalizePropertyValueLists(propname)
else:
@@ -2096,7 +2152,7 @@
if self.name() == "VCALENDAR":
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
component.normalizeAttachments()
else:
@@ -2118,7 +2174,7 @@
@type lookupFunction: L{Function}
"""
for component in self.subcomponents():
- if component.name() == "VTIMEZONE":
+ if component.name() in ignoredComponents:
continue
for prop in itertools.chain(
component.properties("ORGANIZER"),
@@ -2203,7 +2259,49 @@
except KeyError:
pass
+
+ def allPerUserUIDs(self):
+ results = set()
+ for component in self.subcomponents():
+ if component.name() == "X-CALENDARSERVER-PERUSER":
+ results.add(component.propertyValue("X-CALENDARSERVER-PERUSER-UID"))
+ return results
+
+ def perUserTransparency(self, rid):
+
+ # We will create a cache of all user/rid/transparency values as we will likely
+ # be calling this a lot
+ if not hasattr(self, "_perUserTransparency"):
+ self._perUserTransparency = {}
+
+ # Do per-user data
+ for component in self.subcomponents():
+ if component.name() == "X-CALENDARSERVER-PERUSER":
+ uid = component.propertyValue("X-CALENDARSERVER-PERUSER-UID")
+ for subcomponent in component.subcomponents():
+ if subcomponent.name() == "X-CALENDARSERVER-PERINSTANCE":
+ instancerid = subcomponent.propertyValue("RECURRENCE-ID")
+ transp = subcomponent.propertyValue("TRANSP") == "TRANSPARENT"
+ self._perUserTransparency.setdefault(uid, {})[instancerid] = transp
+ elif component.name() not in ignoredComponents:
+ instancerid = component.propertyValue("RECURRENCE-ID")
+ transp = component.propertyValue("TRANSP") == "TRANSPARENT"
+ self._perUserTransparency.setdefault("", {})[instancerid] = transp
+
+ # Now lookup in cache
+ results = []
+ for uid, cachedRids in sorted(self._perUserTransparency.items(), key=lambda x:x[0]):
+ lookupRid = rid
+ if lookupRid not in cachedRids:
+ lookupRid = None
+ if lookupRid in cachedRids:
+ results.append((uid, cachedRids[lookupRid],))
+ else:
+ results.append((uid, False,))
+
+ return tuple(results)
+
##
# Dates and date-times
##
Modified: CalendarServer/trunk/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/icaldav.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/icaldav.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -94,20 +94,6 @@
of type C{"VCALENDAR"}.
"""
- def iCalendarXML(name=None):
- """
- Constructs a CalDAV XML element representing this resource or its child
- with the given name.
- The behavior of this method is not specified if it is called on a
- resource that is not a calendar collection or a calendar resource within
- a calendar collection.
- @param name: the name of the desired child of this resource, or None
- if this resource is desired. Must be None if this resource is
- not a calendar collection.
- @return: a L{twistedcaldav.caldavxml.CalendarData} containing the
- iCalendar data for the requested resource.
- """
-
class ICalendarPrincipalResource(IDAVResource):
"""
CalDAV principle resource.
Modified: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/index.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -47,10 +47,9 @@
from twext.python.log import Logger, LoggingMixIn
from twistedcaldav.ical import Component
-from twistedcaldav.query import calendarquery
+from twistedcaldav.query import calendarquery, queryfilter
from twistedcaldav.sql import AbstractSQLDatabase
from twistedcaldav.sql import db_prefix
-from twistedcaldav import caldavxml
from twistedcaldav.instance import InvalidOverriddenInstanceError
from twistedcaldav.config import config
from twistedcaldav.memcachepool import CachePoolUserMixIn
@@ -58,7 +57,7 @@
log = Logger()
db_basename = db_prefix + "sqlite"
-schema_version = "9"
+schema_version = "10"
collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
icalfbtype_to_indexfbtype = {
@@ -298,7 +297,7 @@
return changed, deleted,
- def indexedSearch(self, filter, fbtype=False):
+ def indexedSearch(self, filter, useruid="", fbtype=False):
"""
Finds resources matching the given qualifiers.
@param filter: the L{Filter} for the calendar-query to execute.
@@ -310,7 +309,7 @@
# Make sure we have a proper Filter element and get the partial SQL
# statement to use.
- if isinstance(filter, caldavxml.Filter):
+ if isinstance(filter, queryfilter.Filter):
qualifiers = calendarquery.sqlcalendarquery(filter)
if qualifiers is not None:
# Determine how far we need to extend the current expansion of
@@ -335,10 +334,25 @@
rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
else:
if fbtype:
+ # Lookup the useruid - try the empty (default) one if needed
+ dbuseruid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID == :1",
+ useruid,
+ )
+ count = self._db_value_for_sql(
+ "select COUNT(PERUSERID) from TRANSPARENCY where PERUSERID == :1",
+ dbuseruid,
+ )
+ if dbuseruid is None or count == 0:
+ dbuseruid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID == :1",
+ "",
+ )
+
# For a free-busy time-range query we return all instances
rowiter = self._db_execute(
- "select RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE" +
- qualifiers[0],
+ "select RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TRANSPARENCY.TRANSPARENT" +
+ qualifiers[0] + " AND TRANSPARENCY.PERUSERID == '%s'" % (dbuseruid,),
*qualifiers[1]
)
else:
@@ -430,6 +444,7 @@
q.execute(
"""
create table RESOURCE (
+ RESOURCEID integer primary key autoincrement,
NAME text unique,
UID text%s,
TYPE text,
@@ -455,11 +470,12 @@
q.execute(
"""
create table TIMESPAN (
- NAME text,
- FLOAT text(1),
- START date,
- END date,
- FBTYPE text(1)
+ INSTANCEID integer primary key autoincrement,
+ RESOURCEID integer,
+ FLOAT text(1),
+ START date,
+ END date,
+ FBTYPE text(1)
)
"""
)
@@ -470,6 +486,41 @@
)
#
+ # PERUSER table tracks per-user ids
+ # PERUSERID: autoincrement primary key
+ # UID: User ID used in calendar data
+ #
+ q.execute(
+ """
+ create table PERUSER (
+ PERUSERID integer primary key autoincrement,
+ USERUID text
+ )
+ """
+ )
+ q.execute(
+ """
+ create index PERUSER_UID on PERUSER (USERUID)
+ """
+ )
+
+ #
+ # TRANSPARENCY table tracks per-user per-instance transparency
+ # PERUSERID: user id key
+ # INSTANCEID: instance id key
+ # TRANSPARENT: Y if transparent, N if opaque
+ #
+ q.execute(
+ """
+ create table TRANSPARENCY (
+ PERUSERID integer,
+ INSTANCEID integer,
+ TRANSPARENT text(1)
+ )
+ """
+ )
+
+ #
# REVISIONS table tracks changes
# NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
# REVISION: revision number
@@ -506,42 +557,42 @@
"""
)
+ # Cascading triggers to help on delete
+ q.execute(
+ """
+ create trigger resourceDelete after delete on RESOURCE
+ for each row
+ begin
+ delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
+ end
+ """
+ )
+ q.execute(
+ """
+ create trigger timespanDelete after delete on TIMESPAN
+ for each row
+ begin
+ delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
+ end
+ """
+ )
+
def _db_can_upgrade(self, old_version):
"""
Can we do an in-place upgrade
"""
- # Previous versions can be upgraded as per _db_upgrade_data_tables
- return True
+ # v10 is a big change - no upgrade possible
+ return False
def _db_upgrade_data_tables(self, q, old_version):
"""
Upgrade the data from an older version of the DB.
"""
- # When going to version 7+ all we need to do is add a column to the resource and timespan
- if old_version < "7":
- q.execute("alter table RESOURCE add column ORGANIZER text default '?'")
- q.execute("alter table TIMESPAN add column FBTYPE text(1) default '?'")
+ # v10 is a big change - no upgrade possible
+ pass
- # When going to version 8+ all we need to do is add an index
- if old_version < "8":
- q.execute("create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)")
-
- # When going to version 9+ all we need to do is add revision table and index
- if old_version < "9":
- q.execute(
- """
- create table REVISIONS (
- NAME text unique,
- REVISION integer,
- CREATEDREVISION integer,
- WASDELETED text(1)
- )
- """
- )
- q.execute("create index REVISION on REVISIONS (REVISION)")
-
def notExpandedBeyond(self, minDate):
"""
Gives all resources which have not been expanded beyond a given date
@@ -598,6 +649,35 @@
self._delete_from_db(name, uid, None)
+ # Add RESOURCE item
+ self._db_execute(
+ """
+ insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
+ values (:1, :2, :3, :4, :5)
+ """, name, uid, calendar.resourceType(), instances.limit, organizer
+ )
+ resourceid = self.lastrowid
+
+ # Get a set of all referenced per-user UIDs and map those to entries already
+ # in the DB and add new ones as needed
+ useruids = calendar.allPerUserUIDs()
+ useruids.add("")
+ useruidmap = {}
+ for useruid in useruids:
+ peruserid = self._db_value_for_sql(
+ "select PERUSERID from PERUSER where USERUID = :1",
+ useruid
+ )
+ if peruserid is None:
+ self._db_execute(
+ """
+ insert into PERUSER (USERUID)
+ values (:1)
+ """, useruid
+ )
+ peruserid = self.lastrowid
+ useruidmap[useruid] = peruserid
+
for key in instances:
instance = instances[key]
start = instance.start.replace(tzinfo=utc)
@@ -605,10 +685,21 @@
float = 'Y' if instance.start.tzinfo is None else 'N'
self._db_execute(
"""
- insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+ insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE)
values (:1, :2, :3, :4, :5)
- """, name, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
+ """, resourceid, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
)
+ instanceid = self.lastrowid
+ peruserdata = calendar.perUserTransparency(instance.rid)
+ for useruid, transp in peruserdata:
+ peruserid = useruidmap[useruid]
+ self._db_execute(
+ """
+ insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+ values (:1, :2, :3)
+ """, peruserid, instanceid, 'T' if transp else 'F'
+ )
+
# Special - for unbounded recurrence we insert a value for "infinity"
# that will allow an open-ended time-range to always match it.
@@ -618,18 +709,21 @@
float = 'N'
self._db_execute(
"""
- insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+ insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE)
values (:1, :2, :3, :4, :5)
- """, name, float, start, end, '?'
+ """, resourceid, float, start, end, '?'
)
+ instanceid = self.lastrowid
+ peruserdata = calendar.perUserTransparency(None)
+ for useruid, transp in peruserdata:
+ peruserid = useruidmap[useruid]
+ self._db_execute(
+ """
+ insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+ values (:1, :2, :3)
+ """, peruserid, instanceid, 'T' if transp else 'F'
+ )
- self._db_execute(
- """
- insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
- values (:1, :2, :3, :4, :5)
- """, name, uid, calendar.resourceType(), instances.limit, organizer
- )
-
if revision is not None:
created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
if created is None:
@@ -647,7 +741,6 @@
@param name: the name of the resource to delete.
@param uid: the uid of the resource to delete.
"""
- self._db_execute("delete from TIMESPAN where NAME = :1", name)
self._db_execute("delete from RESOURCE where NAME = :1", name)
if revision is not None:
self._db_execute(
Modified: CalendarServer/trunk/twistedcaldav/instance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/instance.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/instance.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -326,7 +326,7 @@
# Make sure override RECURRENCE-ID is a valid instance of the master
if got_master:
- if str(rid) not in self.instances and dateordatetime(rid) <= limit:
+ if str(rid) not in self.instances and dateordatetime(rid) < limit:
if self.ignoreInvalidInstances:
return
else:
Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/mail.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -218,8 +218,8 @@
return succeed(self.iMIPACL)
- def resourceType(self):
- return davxml.ResourceType.ischeduleinbox
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.ischeduleinbox)
def isCollection(self):
return False
Modified: CalendarServer/trunk/twistedcaldav/memcacheprops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcacheprops.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/memcacheprops.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -76,7 +76,7 @@
# "/path/to/resource/file":
# (
# {
- # (namespace, name): property,
+ # (namespace, name, uid): property,
# ...,
# },
# memcache_token,
@@ -192,8 +192,8 @@
propertyStore = child.deadProperties()
props = {}
- for qname in propertyStore.list(cache=False):
- props[qname] = propertyStore.get(qname, cache=False)
+ for pnamespace, pname, puid in propertyStore.list(filterByUID=False, cache=False):
+ props[(pnamespace, pname, puid,)] = propertyStore.get((pnamespace, pname,), uid=puid, cache=False)
cache[child.fp.path] = props
@@ -201,16 +201,18 @@
return cache
- def setProperty(self, child, property, delete=False):
+ def setProperty(self, child, property, uid, delete=False):
propertyCache, key, childCache, token = self.childCache(child)
if delete:
qname = property
- if childCache.has_key(qname):
- del childCache[qname]
+ qnameuid = qname + (uid,)
+ if childCache.has_key(qnameuid):
+ del childCache[qnameuid]
else:
qname = property.qname()
- childCache[qname] = property
+ qnameuid = qname + (uid,)
+ childCache[qnameuid] = property
client = self.memcacheClient()
@@ -238,20 +240,24 @@
propertyCache, key, childCache, token = self.childCache(child)
if delete:
- if childCache.has_key(qname):
- del childCache[qname]
+ if childCache.has_key(qnameuid):
+ del childCache[qnameuid]
else:
- childCache[qname] = property
+ childCache[qnameuid] = property
else:
log.error("memcacheprops setProperty had too many failures")
delattr(self, "_propertyCache")
- raise MemcacheError("Unable to %s property {%s}%s on %s"
- % ("delete" if delete else "set",
- qname[0], qname[1], child))
+ raise MemcacheError("Unable to %s property %s{%s}%s on %s" % (
+ "delete" if delete else "set",
+ uid if uid else "",
+ qname[0],
+ qname[1],
+ child
+ ))
- def deleteProperty(self, child, qname):
- return self.setProperty(child, qname, delete=True)
+ def deleteProperty(self, child, qname, uid):
+ return self.setProperty(child, qname, uid, delete=True)
def flushCache(self, child):
path = child.fp.path
@@ -285,49 +291,72 @@
def flushCache(self):
self.parentPropertyCollection.flushCache(self.child)
- def get(self, qname, cache=True):
+ def get(self, qname, uid=None, cache=True):
if cache:
propertyCache = self.propertyCache()
- if qname in propertyCache:
- return propertyCache[qname]
+ qnameuid = qname + (uid,)
+ if qnameuid in propertyCache:
+ return propertyCache[qnameuid]
else:
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
- "No such property: {%s}%s" % qname
+ "No such property: %s{%s}%s" % (uid if uid else "", qname[0], qname[1],)
))
- self.log_debug("Read for %s on %s"
- % (qname, self.childPropertyStore.resource.fp.path))
- return self.childPropertyStore.get(qname)
+ self.log_debug("Read for %s%s on %s" % (
+ ("{%s}:" % (uid,)) if uid else "",
+ qname,
+ self.childPropertyStore.resource.fp.path
+ ))
+ return self.childPropertyStore.get(qname, uid=uid)
- def set(self, property):
- self.log_debug("Write for %s on %s"
- % (property.qname(), self.childPropertyStore.resource.fp.path))
+ def set(self, property, uid=None):
+ self.log_debug("Write for %s%s on %s" % (
+ ("{%s}:" % (uid,)) if uid else "",
+ property.qname(),
+ self.childPropertyStore.resource.fp.path
+ ))
- self.parentPropertyCollection.setProperty(self.child, property)
- self.childPropertyStore.set(property)
+ self.parentPropertyCollection.setProperty(self.child, property, uid)
+ self.childPropertyStore.set(property, uid=uid)
- def delete(self, qname):
- self.log_debug("Delete for %s on %s"
- % (qname, self.childPropertyStore.resource.fp.path))
+ def delete(self, qname, uid=None):
+ self.log_debug("Delete for %s%s on %s" % (
+ ("{%s}:" % (uid,)) if uid else "",
+ qname,
+ self.childPropertyStore.resource.fp.path
+ ))
- self.parentPropertyCollection.deleteProperty(self.child, qname)
- self.childPropertyStore.delete(qname)
+ self.parentPropertyCollection.deleteProperty(self.child, qname, uid)
+ self.childPropertyStore.delete(qname, uid=uid)
- def contains(self, qname, cache=True):
+ def contains(self, qname, uid=None, cache=True):
if cache:
propertyCache = self.propertyCache()
- return qname in propertyCache
+ qnameuid = qname + (uid,)
+ return qnameuid in propertyCache
- self.log_debug("Contains for %s"
- % (self.childPropertyStore.resource.fp.path,))
- return self.childPropertyStore.contains(qname)
+ self.log_debug("Contains for %s%s on %s" % (
+ ("{%s}:" % (uid,)) if uid else "",
+ qname,
+ self.childPropertyStore.resource.fp.path,
+ ))
+ return self.childPropertyStore.contains(qname, uid=uid)
- def list(self, cache=True):
+ def list(self, uid=None, filterByUID=True, cache=True):
if cache:
propertyCache = self.propertyCache()
- return propertyCache.iterkeys()
+ results = propertyCache.keys()
+ if filterByUID:
+ return [
+ (namespace, name)
+ for namespace, name, propuid in results
+ if propuid == uid
+ ]
+ else:
+ return results
+
self.log_debug("List for %s"
% (self.childPropertyStore.resource.fp.path,))
- return self.childPropertyStore.list()
+ return self.childPropertyStore.list(uid=uid, filterByUID=filterByUID)
Modified: CalendarServer/trunk/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -143,7 +143,7 @@
lock = None
if not self.internal_request and self.allowImplicitSchedule:
# Get data we need for implicit scheduling
- calendar = delresource.iCalendar()
+ calendar = (yield delresource.iCalendarForUser(self.request))
scheduler = ImplicitScheduler()
do_implicit_action, _ignore = (yield scheduler.testImplicitSchedulingDELETE(self.request, delresource, calendar))
if do_implicit_action:
@@ -214,6 +214,18 @@
errors.add(childurl, responsecode.BAD_REQUEST)
# Now do normal delete
+
+ # Check virtual share first
+ isVirtual = yield delresource.isVirtualShare(self.request)
+ if isVirtual:
+ yield delresource.removeVirtualShare(self.request)
+ returnValue(responsecode.NO_CONTENT)
+
+ # Handle sharing
+ wasShared = (yield delresource.isShared(self.request))
+ if wasShared:
+ yield delresource.downgradeFromShare(self.request)
+
# Change CTag
yield delresource.bumpSyncToken()
more_responses = (yield self.deleteResource(delresource, deluri, parent))
@@ -348,6 +360,12 @@
errors.add(childurl, responsecode.BAD_REQUEST)
# Now do normal delete
+
+ # Handle sharing
+ wasShared = (yield delresource.isShared(self.request))
+ if wasShared:
+ yield delresource.downgradeFromShare(self.request)
+
yield delresource.updateCTag()
more_responses = (yield self.deleteResource(delresource, deluri, parent))
Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/get.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -22,52 +22,56 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twext.web2.dav import davxml
+from twext.web2.dav.util import parentForURL
from twext.web2.http import HTTPError
from twext.web2.http import Response
from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
-from twistedcaldav import caldavxml
from twistedcaldav.caldavxml import ScheduleTag
from twistedcaldav.customxml import TwistedCalendarAccessProperty
-from twistedcaldav.ical import Component
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.resource import isPseudoCalendarCollectionResource
@inlineCallbacks
def http_GET(self, request):
# Look for calendar access restriction on existing resource.
if self.exists():
- try:
- access = self.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
-
- if access in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+ parentURL = parentForURL(request.uri)
+ parent = (yield request.locateResource(parentURL))
+ if isPseudoCalendarCollectionResource(parent):
# Check authorization first
yield self.authorize(request, (davxml.Read(),))
- # Non DAV:owner's have limited access to the data
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-
- if not isowner:
- # Now "filter" the resource calendar data through the CALDAV:calendar-data element and apply
- # access restrictions to the data.
- caldata = caldavxml.CalendarData().elementFromResourceWithAccessRestrictions(self, access).calendarData()
+ caldata = (yield self.iCalendarForUser(request))
- response = Response()
- response.stream = MemoryStream(caldata)
- response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
- returnValue(response)
+ try:
+ access = self.readDeadProperty(TwistedCalendarAccessProperty)
+ except HTTPError:
+ access = None
+
+ if access:
+
+ # Non DAV:owner's have limited access to the data
+ isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+
+ # Now "filter" the resource calendar data
+ caldata = PrivateEventFilter(access, isowner).filter(caldata)
+
+ response = Response()
+ response.stream = MemoryStream(str(caldata))
+ response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+
+ # Add Schedule-Tag header if property is present
+ if self.hasDeadProperty(ScheduleTag):
+ scheduletag = self.readDeadProperty(ScheduleTag)
+ if scheduletag:
+ response.headers.setHeader("Schedule-Tag", str(scheduletag))
+
+ returnValue(response)
-
# Do normal GET behavior
response = (yield super(CalDAVFile, self).http_GET(request))
-
- # Add Schedule-Tag header if property is present
- if self.exists() and self.hasDeadProperty(ScheduleTag):
- scheduletag = self.readDeadProperty(ScheduleTag)
- if scheduletag:
- response.headers.setHeader("Schedule-Tag", str(scheduletag))
-
returnValue(response)
Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -165,11 +165,10 @@
# Now handle other properties
for property in mkcol.children[0].children[0].children:
try:
- if rtype == "calendar":
- if property.qname() == (caldavxml.caldav_namespace, "supported-calendar-component-set"):
- self.writeDeadProperty(property)
- elif not isinstance(property, davxml.ResourceType):
- yield self.writeProperty(property, request)
+ if rtype == "calendar" and property.qname() == (caldavxml.caldav_namespace, "supported-calendar-component-set"):
+ self.writeDeadProperty(property)
+ else:
+ yield self.writeProperty(property, request)
except HTTPError:
errors.add(Failure(), property)
got_an_error = True
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -55,6 +55,7 @@
TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
TwistedScheduleMatchETags
from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.fileops import copyToWithXAttrs, copyXAttrs
from twistedcaldav.fileops import putWithXAttrs
from twistedcaldav.fileops import copyWithXAttrs
@@ -315,7 +316,11 @@
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
# At this point we need the calendar data to do more tests
- self.calendar = self.source.iCalendar()
+ try:
+ self.calendar = (yield self.source.iCalendarForUser(self.request))
+ except ValueError, e:
+ log.err(str(e))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="Can't parse calendar data"))
else:
try:
if type(self.calendar) in (types.StringType, types.UnicodeType,):
@@ -364,7 +369,7 @@
# FIXME: We need this here because we have to re-index the destination. Ideally it
# would be better to copy the index entries from the source and add to the destination.
- self.calendar = self.source.iCalendar()
+ self.calendar = (yield self.source.iCalendarForUser(self.request))
# Check access
if self.destinationcal and config.EnablePrivateEvents:
@@ -375,7 +380,7 @@
elif self.sourcecal:
self.source_index = self.sourceparent.index()
- self.calendar = self.source.iCalendar()
+ self.calendar = (yield self.source.iCalendarForUser(self.request))
@inlineCallbacks
def validCopyMoveOperation(self):
@@ -692,6 +697,7 @@
else:
return False
+ @inlineCallbacks
def preservePrivateComments(self):
# Check for private comments on the old resource and the new resource and re-insert
# ones that are lost.
@@ -709,14 +715,14 @@
if old_has_private_comments and not new_has_private_comments:
# Transfer old comments to new calendar
log.debug("Private Comments properties were entirely removed by the client. Restoring existing properties.")
- old_calendar = self.destination.iCalendar()
+ old_calendar = (yield self.destination.iCalendarForUser(self.request))
self.calendar.transferProperties(old_calendar, (
"X-CALENDARSERVER-PRIVATE-COMMENT",
"X-CALENDARSERVER-ATTENDEE-COMMENT",
))
self.calendardata = None
- return new_has_private_comments
+ returnValue(new_has_private_comments)
@inlineCallbacks
def doImplicitScheduling(self):
@@ -789,7 +795,26 @@
returnValue((is_scheduling_resource, data_changed, did_implicit_action,))
@inlineCallbacks
+ def mergePerUserData(self):
+
+ if self.calendar:
+ accessUID = (yield self.destination.resourceOwnerPrincipal(self.request))
+ accessUID = accessUID.principalUID() if accessUID else ""
+ oldCal = self.destination.iCalendar() if self.destination.exists() and self.destinationcal else None
+
+ # Duplicate before we do the merge because someone else may "own" the calendar object
+ # and we should not change it. This is not ideal as we may duplicate it unnecessarily
+ # but we currently have no api to let the caller tell us whether it cares about the
+ # whether the calendar data is changed or not.
+ self.calendar = PerUserDataFilter(accessUID).merge(self.calendar.duplicate(), oldCal)
+ self.calendardata = None
+
+ @inlineCallbacks
def doStore(self, implicit):
+
+ # Always do the per-user data merge right before we store
+ yield self.mergePerUserData()
+
# Do put or copy based on whether source exists
if self.source is not None:
if implicit:
@@ -966,7 +991,7 @@
rruleChanged = self.truncateRecurrence()
# Preserve private comments
- new_has_private_comments = self.preservePrivateComments()
+ new_has_private_comments = (yield self.preservePrivateComments())
# Do scheduling
implicit_result = (yield self.doImplicitScheduling())
Modified: CalendarServer/trunk/twistedcaldav/method/report_calquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calquery.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/report_calquery.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -40,6 +40,7 @@
from twistedcaldav.index import IndexedSearchException
from twistedcaldav.instance import TooManyInstancesError
from twistedcaldav.method import report_common
+from twistedcaldav.query import queryfilter
log = Logger()
@@ -64,10 +65,11 @@
responses = []
- filter = calendar_query.filter
- query = calendar_query.query
+ xmlfilter = calendar_query.filter
+ filter = queryfilter.Filter(xmlfilter)
+ props = calendar_query.props
- assert query is not None
+ assert props is not None
# Get the original timezone provided in the query, if any, and validate it now
query_timezone = None
@@ -80,19 +82,19 @@
filter.settimezone(query_tz)
query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0]
- if query.qname() == ("DAV:", "allprop"):
+ if props.qname() == ("DAV:", "allprop"):
propertiesForResource = report_common.allPropertiesForResource
generate_calendar_data = False
- elif query.qname() == ("DAV:", "propname"):
+ elif props.qname() == ("DAV:", "propname"):
propertiesForResource = report_common.propertyNamesForResource
generate_calendar_data = False
- elif query.qname() == ("DAV:", "prop"):
+ elif props.qname() == ("DAV:", "prop"):
propertiesForResource = report_common.propertyListForResource
# Verify that any calendar-data element matches what we can handle
- result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(query)
+ result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props)
if not result:
log.err(message)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
@@ -102,7 +104,7 @@
# Verify that the filter element is valid
if (filter is None) or not filter.valid():
- log.err("Invalid filter element: %r" % (filter,))
+ log.err("Invalid filter element: %r" % (xmlfilter,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-filter")))
matchcount = [0]
@@ -145,7 +147,7 @@
else:
href = davxml.HRef.fromString(uri)
- return report_common.responseForHref(request, responses, href, resource, calendar, timezone, propertiesForResource, query, isowner)
+ return report_common.responseForHref(request, responses, href, resource, calendar, timezone, propertiesForResource, props, isowner)
else:
return succeed(None)
@@ -200,7 +202,7 @@
child_path_name = urllib.unquote(child_uri_name)
if generate_calendar_data or not index_query_ok:
- calendar = calresource.iCalendar(child_path_name)
+ calendar = (yield calresource.iCalendarForUser(request, child_path_name))
assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self)
else:
calendar = None
@@ -224,7 +226,7 @@
# Check private events access status
isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
- calendar = calresource.iCalendar()
+ calendar = (yield calresource.iCalendarForUser(request))
yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)
returnValue(True)
Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -55,12 +55,15 @@
from twistedcaldav import caldavxml
from twistedcaldav import carddavxml
-from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.caldavxml import caldav_namespace, CalendarData
from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap
from twistedcaldav.ical import Component, Property, iCalendarProductID
from twistedcaldav.instance import InstanceList
from twistedcaldav.index import IndexedSearchException
+from twistedcaldav.query import queryfilter
log = Logger()
@@ -335,20 +338,16 @@
for property in props:
if isinstance(property, caldavxml.CalendarData):
# Handle private events access restrictions
- if not isowner:
- try:
- access = resource.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
- else:
+ try:
+ access = resource.readDeadProperty(TwistedCalendarAccessProperty)
+ except HTTPError:
access = None
- if calendar:
- propvalue = property.elementFromCalendarWithAccessRestrictions(calendar, access, timezone)
- else:
- propvalue = property.elementFromResourceWithAccessRestrictions(resource, access, timezone)
- if propvalue is None:
- raise ValueError("Invalid CalDAV:calendar-data for request: %r" % (property,))
+ if calendar is None:
+ calendar = (yield resource.iCalendarForUser(request))
+ filtered = PrivateEventFilter(access, isowner).filter(calendar)
+ filtered = CalendarDataFilter(property, timezone).filter(filtered)
+ propvalue = CalendarData().fromCalendar(filtered)
properties_by_status[responsecode.OK].append(propvalue)
continue
@@ -438,6 +437,7 @@
name="VCALENDAR",
)
)
+ filter = queryfilter.Filter(filter)
# Get the timezone property from the collection, and store in the query filter
# for use during the query itself.
@@ -453,13 +453,17 @@
filteredaces = (yield calresource.inheritedACEsforChildren(request))
try:
- resources = calresource.index().indexedSearch(filter, fbtype=True)
+ useruid = (yield calresource.resourceOwnerPrincipal(request))
+ useruid = useruid.principalUID() if useruid else ""
+ resources = calresource.index().indexedSearch(filter, useruid=useruid, fbtype=True)
except IndexedSearchException:
resources = calresource.index().bruteForceSearch()
# We care about separate instances for VEVENTs only
aggregated_resources = {}
- for name, uid, type, test_organizer, float, start, end, fbtype in resources:
+ for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+ if transp == 'T' and fbtype != '?':
+ fbtype = 'F'
aggregated_resources.setdefault((name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))
for key in aggregated_resources.iterkeys():
@@ -528,7 +532,7 @@
raise NumberOfMatchesWithinLimits(max_number_of_matches)
else:
- calendar = calresource.iCalendar(name)
+ calendar = (yield calresource.iCalendarForUser(request, name))
# The calendar may come back as None if the resource is being changed, or was deleted
# between our initial index query and getting here. For now we will ignore this error, but in
Copied: CalendarServer/trunk/twistedcaldav/notifications.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/notifications.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,218 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Implements notification functionality.
+"""
+
+__all__ = [
+ "NotificationResource",
+ "NotificationCollectionResource",
+]
+
+from twext.python.log import Logger, LoggingMixIn
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import DAVResource
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+import os
+import types
+
+log = Logger()
+
+class NotificationResource(DAVResource):
+ """
+ An xml resource in a Notification collection.
+ """
+ def __init__(self, parent):
+ self._parent = parent
+ DAVResource.__init__(self)
+
+ def principalCollections(self):
+ return self._parent.principalCollections()
+
+ def isCollection(self):
+ return False
+
+ def resourceName(self):
+ raise NotImplementedError
+
+ def http_PUT(self, request):
+ return responsecode.FORBIDDEN
+
+ @inlineCallbacks
+ def http_DELETE(self, request):
+
+ response = (yield super(NotificationResource, self).http_DELETE(request))
+ if response == responsecode.NO_CONTENT:
+ yield self._parent.removedNotifictionMessage(request, self.resourceName())
+ returnValue(response)
+
+class NotificationCollectionResource(DAVResource):
+
+ def notificationsDB(self):
+
+ if not hasattr(self, "_notificationsDB"):
+ self._notificationsDB = NotificationsDatabase(self)
+ return self._notificationsDB
+
+ def isCollection(self):
+ return True
+
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.notification)
+
+ @inlineCallbacks
+ def addNotification(self, request, uid, xmltype, xmldata):
+
+ # Write data to file
+ rname = uid + ".xml"
+ yield self._writeNotification(request, uid, rname, xmltype, xmldata)
+
+ # Update database
+ self.notificationsDB().addOrUpdateRecord(NotificationRecord(uid, rname, xmltype.name))
+
+ def _writeNotification(self, request, uid, rname, xmltype, xmldata):
+ raise NotImplementedError
+
+ def getNotifictionMessages(self, request, componentType=None, returnLatestVersion=True):
+ return succeed([])
+
+ def getNotifictionMessageByUID(self, request, uid):
+ return succeed(self.notificationsDB().recordForUID(uid))
+
+ @inlineCallbacks
+ def deleteNotifictionMessageByUID(self, request, uid):
+
+ # See if it exists and delete the resource
+ record = self.notificationsDB().recordForUID(uid)
+ if record:
+ yield self._deleteNotification(request, record.name)
+ self.notificationsDB().removeRecordForUID(record.uid)
+
+ def deleteNotifictionMessageByName(self, request, rname):
+
+ # See if it exists and delete the resource
+ record = self.notificationsDB().recordForName(rname)
+ if record:
+ self._deleteNotification(request, record.name)
+ self.notificationsDB().removeRecordForUID(record.uid)
+
+ return succeed(None)
+
+ def removedNotifictionMessage(self, request, rname):
+ self.notificationsDB().removeRecordForName(rname)
+ return succeed(None)
+
+class NotificationRecord(object):
+
+ def __init__(self, uid, name, xmltype):
+ self.uid = uid
+ self.name = name
+ self.xmltype = xmltype
+
+class NotificationsDatabase(AbstractSQLDatabase, LoggingMixIn):
+
+ db_basename = db_prefix + "notifications"
+ schema_version = "1"
+ db_type = "notifications"
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+ the notifications collection.)
+ """
+ self.resource = resource
+ db_filename = os.path.join(self.resource.fp.path, NotificationsDatabase.db_basename)
+ super(NotificationsDatabase, self).__init__(db_filename, True, autocommit=True)
+
+ def allRecords(self):
+
+ records = self._db_execute("select * from NOTIFICATIONS")
+ return [self._makeRecord(row) for row in (records if records is not None else ())]
+
+ def recordForUID(self, uid):
+
+ row = self._db_execute("select * from NOTIFICATIONS where UID = :1", uid)
+ return self._makeRecord(row[0]) if row else None
+
+ def addOrUpdateRecord(self, record):
+
+ self._db_execute("""insert or replace into NOTIFICATIONS (UID, NAME, TYPE)
+ values (:1, :2, :3)
+ """, record.uid, record.name, record.xmltype,
+ )
+
+ def removeRecordForUID(self, uid):
+
+ self._db_execute("delete from NOTIFICATIONS where UID = :1", uid)
+
+ def removeRecordForName(self, rname):
+
+ self._db_execute("delete from NOTIFICATIONS where NAME = :1", rname)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return NotificationsDatabase.schema_version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return NotificationsDatabase.db_type
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # NOTIFICATIONS table is the primary table
+ # UID: UID for this notification
+ # NAME: child resource name
+ # TYPE: type of notification
+ #
+ q.execute(
+ """
+ create table NOTIFICATIONS (
+ UID text unique,
+ NAME text unique,
+ TYPE text
+ )
+ """
+ )
+
+ q.execute(
+ """
+ create index UID on NOTIFICATIONS (UID)
+ """
+ )
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # Nothing to do as we have not changed the schema
+ pass
+
+ def _makeRecord(self, row):
+
+ return NotificationRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+
Modified: CalendarServer/trunk/twistedcaldav/query/calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/calendarquery.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/query/calendarquery.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -27,9 +27,7 @@
]
from twistedcaldav.dateops import floatoffset
-from twistedcaldav.query import sqlgenerator
-from twistedcaldav.query import expression
-from twistedcaldav import caldavxml
+from twistedcaldav.query import expression, sqlgenerator, queryfilter
# SQL Index column (field) names
@@ -51,17 +49,17 @@
# Lets assume we have a valid filter from the outset.
# Top-level filter contains exactly one comp-filter element
- assert len(filter.children) == 1
- vcalfilter = filter.children[0]
- assert isinstance(vcalfilter, caldavxml.ComponentFilter)
+ assert filter.child is not None
+ vcalfilter = filter.child
+ assert isinstance(vcalfilter, queryfilter.ComponentFilter)
assert vcalfilter.filter_name == "VCALENDAR"
- if len(vcalfilter.children) > 0:
+ if len(vcalfilter.filters) > 0:
# Only comp-filters are handled
- for _ignore in [x for x in vcalfilter.children if not isinstance(x, caldavxml.ComponentFilter)]:
+ for _ignore in [x for x in vcalfilter.filters if not isinstance(x, queryfilter.ComponentFilter)]:
raise ValueError
- return compfilterListExpression(vcalfilter.children)
+ return compfilterListExpression(vcalfilter.filters)
else:
return expression.allExpression()
@@ -98,13 +96,13 @@
expressions.append(expression.inExpression(FIELD_TYPE, compfilter.filter_name, True))
# Handle time-range
- if compfilter.qualifier and isinstance(compfilter.qualifier, caldavxml.TimeRange):
+ if compfilter.qualifier and isinstance(compfilter.qualifier, queryfilter.TimeRange):
start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier)
expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat))
# Handle properties - we can only do UID right now
props = []
- for p in [x for x in compfilter.filters if isinstance(x, caldavxml.PropertyFilter)]:
+ for p in [x for x in compfilter.filters if isinstance(x, queryfilter.PropertyFilter)]:
props.append(propfilterExpression(p))
if len(props) > 1:
propsExpression = expression.orExpression[props]
@@ -115,7 +113,7 @@
# Handle embedded components - we do not right now as our Index does not handle them
comps = []
- for _ignore in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]:
+ for _ignore in [x for x in compfilter.filters if isinstance(x, queryfilter.ComponentFilter)]:
raise ValueError
if len(comps) > 1:
compsExpression = expression.orExpression[comps]
@@ -153,16 +151,16 @@
return expression.isExpression(FIELD_UID, "", True)
# Handle time-range - we cannot do this with our Index right now
- if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TimeRange):
+ if propfilter.qualifier and isinstance(propfilter.qualifier, queryfilter.TimeRange):
raise ValueError
# Handle text-match
tm = None
- if propfilter.qualifier and isinstance(propfilter.qualifier, caldavxml.TextMatch):
+ if propfilter.qualifier and isinstance(propfilter.qualifier, queryfilter.TextMatch):
if propfilter.qualifier.negate:
- tm = expression.notcontainsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier)
+ tm = expression.notcontainsExpression(propfilter.filter_name, propfilter.qualifier.text, propfilter.qualifier.caseless)
else:
- tm = expression.containsExpression(propfilter.filter_name, str(propfilter.qualifier), propfilter.qualifier)
+ tm = expression.containsExpression(propfilter.filter_name, propfilter.qualifier.text, propfilter.qualifier.caseless)
# Handle embedded parameters - we do not right now as our Index does not handle them
params = []
@@ -227,79 +225,3 @@
return sql.generate()
except ValueError:
return None
-
-
-if __name__ == "__main__":
- import datetime
-
- filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- *[caldavxml.ComponentFilter(
- *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
- **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
- )],
- **{"name":"VCALENDAR"}
- )
- )
-
- # A complete implementation of current DST rules for major US time zones.
-
- def first_sunday_on_or_after(dt):
- days_to_go = 6 - dt.weekday()
- if days_to_go:
- dt += datetime.timedelta(days_to_go)
- return dt
-
- # In the US, DST starts at 2am (standard time) on the first Sunday in April.
- DSTSTART = datetime.datetime(1, 4, 1, 2)
- # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
- # which is the first Sunday on or after Oct 25.
- DSTEND = datetime.datetime(1, 10, 25, 1)
-
- ZERO = datetime.timedelta(0)
- HOUR = datetime.timedelta(hours=1)
-
- class USTimeZone(datetime.tzinfo):
-
- def __init__(self, hours, reprname, stdname, dstname):
- self.stdoffset = datetime.timedelta(hours=hours)
- self.reprname = reprname
- self.stdname = stdname
- self.dstname = dstname
-
- def __repr__(self):
- return self.reprname
-
- def tzname(self, dt):
- if self.dst(dt):
- return self.dstname
- else:
- return self.stdname
-
- def utcoffset(self, dt):
- return self.stdoffset + self.dst(dt)
-
- def dst(self, dt):
- if dt is None or dt.tzinfo is None:
- # An exception may be sensible here, in one or both cases.
- # It depends on how you want to treat them. The default
- # fromutc() implementation (called by the default astimezone()
- # implementation) passes a datetime with dt.tzinfo is self.
- return ZERO
- assert dt.tzinfo is self
-
- # Find first Sunday in April & the last in October.
- start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
- end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
-
- # Can't compare naive to aware objects, so strip the timezone from
- # dt first.
- if start <= dt.replace(tzinfo=None) < end:
- return HOUR
- else:
- return ZERO
-
- Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
- filter.children[0].settzinfo(Eastern)
-
- print sqlcalendarquery(filter)
Copied: CalendarServer/trunk/twistedcaldav/query/queryfilter.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/queryfilter.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/queryfilter.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/queryfilter.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,650 @@
+##
+# Copyright (c) 2009 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.
+##
+
+"""
+Object model of CALDAV:filter element used in a calendar-query.
+"""
+
+__all__ = [
+ "Filter",
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
+from twistedcaldav.dateops import timeRangesOverlap
+from twistedcaldav.ical import Component, Property, parse_date_or_datetime
+from vobject.icalendar import utc
+import datetime
+
+log = Logger()
+
+class FilterBase(object):
+ """
+ Determines which matching components are returned.
+ """
+
+ def __init__(self, xml_element):
+ self.xmlelement = xml_element
+
+ def match(self, item, access=None):
+ raise NotImplementedError
+
+ def valid(self, level=0):
+ raise NotImplementedError
+
+class Filter(FilterBase):
+ """
+ Determines which matching components are returned.
+ """
+
+ def __init__(self, xml_element):
+
+ super(Filter, self).__init__(xml_element)
+
+ # One comp-filter element must be present
+ if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, "comp-filter"):
+ raise ValueError("Invalid CALDAV:filter element: %s" % (xml_element,))
+
+ self.child = ComponentFilter(xml_element.children[0])
+
+ def match(self, component, access=None):
+ """
+ Returns True if the given calendar component matches this filter, False
+ otherwise.
+ """
+
+ # We only care about certain access restrictions.
+ if access not in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+ access = None
+
+ # We need to prepare ourselves for a time-range query by pre-calculating
+ # the set of instances up to the latest time-range limit. That way we can
+ # avoid having to do some form of recurrence expansion for each query sub-part.
+ maxend, isStartTime = self.getmaxtimerange()
+ if maxend:
+ if isStartTime:
+ if component.isRecurringUnbounded():
+ # Unbounded recurrence is always within a start-only time-range
+ instances = None
+ else:
+ # Expand the instances up to infinity
+ instances = component.expandTimeRanges(datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc), ignoreInvalidInstances=True)
+ else:
+ instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
+ else:
+ instances = None
+ self.child.setInstances(instances)
+
+ # <filter> contains exactly one <comp-filter>
+ return self.child.match(component, access)
+
+ def valid(self):
+ """
+ Indicate whether this filter element's structure is valid wrt iCalendar
+ data object model.
+
+ @return: True if valid, False otherwise
+ """
+
+ # Must have one child element for VCALENDAR
+ return self.child.valid(0)
+
+ def settimezone(self, tzelement):
+ """
+ Set the default timezone to use with this query.
+ @param calendar: a L{Component} for the VCALENDAR containing the one
+ VTIMEZONE that we want
+ @return: the L{datetime.tzinfo} derived from the VTIMEZONE or utc.
+ """
+ assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
+
+ if tzelement is not None:
+ calendar = tzelement.calendar()
+ if calendar is not None:
+ for subcomponent in calendar.subcomponents():
+ if subcomponent.name() == "VTIMEZONE":
+ # <filter> contains exactly one <comp-filter>
+ tzinfo = subcomponent.gettzinfo()
+ self.child.settzinfo(tzinfo)
+ return tzinfo
+
+ # Default to using utc tzinfo
+ self.child.settzinfo(utc)
+ return utc
+
+ def getmaxtimerange(self):
+ """
+ Get the date farthest into the future in any time-range elements
+ """
+
+ return self.child.getmaxtimerange(None, False)
+
+class FilterChildBase(FilterBase):
+ """
+ CalDAV filter element.
+ """
+
+ def __init__(self, xml_element):
+
+ super(FilterChildBase, self).__init__(xml_element)
+
+ qualifier = None
+ filters = []
+
+ for child in xml_element.children:
+ qname = child.qname()
+
+ if qname in (
+ (caldav_namespace, "is-not-defined"),
+ (caldav_namespace, "time-range"),
+ (caldav_namespace, "text-match"),
+ ):
+ if qualifier is not None:
+ raise ValueError("Only one of CalDAV:time-range, CalDAV:text-match allowed")
+
+ if qname == (caldav_namespace, "is-not-defined"):
+ qualifier = IsNotDefined(child)
+ elif qname == (caldav_namespace, "time-range"):
+ qualifier = TimeRange(child)
+ elif qname == (caldav_namespace, "text-match"):
+ qualifier = TextMatch(child)
+
+ elif qname == (caldav_namespace, "comp-filter"):
+ filters.append(ComponentFilter(child))
+ elif qname == (caldav_namespace, "prop-filter"):
+ filters.append(PropertyFilter(child))
+ elif qname == (caldav_namespace, "param-filter"):
+ filters.append(ParameterFilter(child))
+ else:
+ raise ValueError("Unknown child element: %s" % (qname,))
+
+ if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+ raise ValueError("No other tests allowed when CalDAV:is-not-defined is present")
+
+ self.qualifier = qualifier
+ self.filters = filters
+ self.filter_name = xml_element.attributes["name"]
+ if isinstance(self.filter_name, unicode):
+ self.filter_name = self.filter_name.encode("utf-8")
+ self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+ def match(self, item, access=None):
+ """
+ Returns True if the given calendar item (either a component, property or parameter value)
+ matches this filter, False otherwise.
+ """
+
+ # Always return True for the is-not-defined case as the result of this will
+ # be negated by the caller
+ if not self.defined: return True
+
+ if self.qualifier and not self.qualifier.match(item, access): return False
+
+ if len(self.filters) > 0:
+ for filter in self.filters:
+ if filter._match(item, access):
+ return True
+ return False
+ else:
+ return True
+
+class ComponentFilter (FilterChildBase):
+ """
+ Limits a search to only the chosen component types.
+ """
+
+ def match(self, item, access):
+ """
+ Returns True if the given calendar item (which is a component)
+ matches this filter, False otherwise.
+ This specialization uses the instance matching option of the time-range filter
+ to minimize instance expansion.
+ """
+
+ # Always return True for the is-not-defined case as the result of this will
+ # be negated by the caller
+ if not self.defined: return True
+
+ if self.qualifier and not self.qualifier.matchinstance(item, self.instances): return False
+
+ if len(self.filters) > 0:
+ for filter in self.filters:
+ if filter._match(item, access):
+ return True
+ return False
+ else:
+ return True
+
+ def _match(self, component, access):
+ # At least one subcomponent must match (or is-not-defined is set)
+ for subcomponent in component.subcomponents():
+ # If access restrictions are in force, restrict matching to specific components only.
+ # In particular do not match VALARM.
+ if access and subcomponent.name() not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE",):
+ continue
+
+ # Try to match the component name
+ if isinstance(self.filter_name, str):
+ if subcomponent.name() != self.filter_name: continue
+ else:
+ if subcomponent.name() not in self.filter_name: continue
+ if self.match(subcomponent, access): break
+ else:
+ return not self.defined
+ return self.defined
+
+ def setInstances(self, instances):
+ """
+ Give the list of instances to each comp-filter element.
+ @param instances: the list of instances.
+ """
+ self.instances = instances
+ for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+ compfilter.setInstances(instances)
+
+ def valid(self, level):
+ """
+ Indicate whether this filter element's structure is valid wrt iCalendar
+ data object model.
+
+ @param level: the nesting level of this filter element, 0 being the top comp-filter.
+ @return: True if valid, False otherwise
+ """
+
+ # Check for time-range
+ timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+ if level == 0:
+ # Must have VCALENDAR at the top
+ if (self.filter_name != "VCALENDAR") or timerange:
+ log.msg("Top-level comp-filter must be VCALENDAR, instead: %s" % (self.filter_name,))
+ return False
+ elif level == 1:
+ # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
+ if self.filter_name in ("VCALENDAR", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE"):
+ log.msg("comp-filter wrong component type: %s" % (self.filter_name,))
+ return False
+
+ # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
+ if timerange and self.filter_name not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY"):
+ log.msg("time-range cannot be used with component %s" % (self.filter_name,))
+ return False
+ elif level == 2:
+ # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
+ if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VAVAILABILITY")):
+ log.msg("comp-filter wrong sub-component type: %s" % (self.filter_name,))
+ return False
+
+ # time-range only on VALARM, AVAILABLE
+ if timerange and self.filter_name not in ("VALARM", "AVAILABLE",):
+ log.msg("time-range cannot be used with sub-component %s" % (self.filter_name,))
+ return False
+ else:
+ # Disallow all standard iCal components anywhere else
+ if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VALARM", "STANDARD", "DAYLIGHT", "AVAILABLE")) or timerange:
+ log.msg("comp-filter wrong standard component type: %s" % (self.filter_name,))
+ return False
+
+ # Test each property
+ for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
+ if not propfilter.valid():
+ return False
+
+ # Test each component
+ for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+ if not compfilter.valid(level + 1):
+ return False
+
+ # Test the time-range
+ if timerange:
+ if not self.qualifier.valid():
+ return False
+
+ return True
+
+ def settzinfo(self, tzinfo):
+ """
+ Set the default timezone to use with this query.
+ @param tzinfo: a L{datetime.tzinfo} to use.
+ """
+
+ # Give tzinfo to any TimeRange we have
+ if isinstance(self.qualifier, TimeRange):
+ self.qualifier.settzinfo(tzinfo)
+
+ # Pass down to sub components/properties
+ for x in self.filters:
+ x.settzinfo(tzinfo)
+
+ def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+ """
+ Get the date furthest into the future in any time-range elements
+
+ @param currentMaximum: current future value to compare with
+ @type currentMaximum: L{datetime.datetime}
+ """
+
+ # Give tzinfo to any TimeRange we have
+ isStartTime = False
+ if isinstance(self.qualifier, TimeRange):
+ isStartTime = self.qualifier.end is None
+ compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+ if currentMaximum is None or currentMaximum < compareWith:
+ currentMaximum = compareWith
+ currentIsStartTime = isStartTime
+
+ # Pass down to sub components/properties
+ for x in self.filters:
+ currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
+
+ return currentMaximum, currentIsStartTime
+
+class PropertyFilter (FilterChildBase):
+ """
+ Limits a search to specific properties.
+ """
+
+ def _match(self, component, access):
+ # When access restriction is in force, we need to only allow matches against the properties
+ # allowed by the access restriction level.
+ if access:
+ allowedProperties = Component.confidentialPropertiesMap.get(component.name(), None)
+ if allowedProperties and access == Component.ACCESS_RESTRICTED:
+ allowedProperties += Component.extraRestrictedProperties
+ else:
+ allowedProperties = None
+
+ # At least one property must match (or is-not-defined is set)
+ for property in component.properties():
+ # Apply access restrictions, if any.
+ if allowedProperties is not None and property.name() not in allowedProperties:
+ continue
+ if property.name() == self.filter_name and self.match(property, access): break
+ else:
+ return not self.defined
+ return self.defined
+
+ def valid(self):
+ """
+ Indicate whether this filter element's structure is valid wrt iCalendar
+ data object model.
+
+ @return: True if valid, False otherwise
+ """
+
+ # Check for time-range
+ timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+ # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
+ if timerange and self.filter_name not in ("COMPLETED", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
+ log.msg("time-range cannot be used with property %s" % (self.filter_name,))
+ return False
+
+ # Test the time-range
+ if timerange:
+ if not self.qualifier.valid():
+ return False
+
+ # No other tests
+ return True
+
+ def settzinfo(self, tzinfo):
+ """
+ Set the default timezone to use with this query.
+ @param tzinfo: a L{datetime.tzinfo} to use.
+ """
+
+ # Give tzinfo to any TimeRange we have
+ if isinstance(self.qualifier, TimeRange):
+ self.qualifier.settzinfo(tzinfo)
+
+ def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+ """
+ Get the date furthest into the future in any time-range elements
+
+ @param currentMaximum: current future value to compare with
+ @type currentMaximum: L{datetime.datetime}
+ """
+
+ # Give tzinfo to any TimeRange we have
+ isStartTime = False
+ if isinstance(self.qualifier, TimeRange):
+ isStartTime = self.qualifier.end is None
+ compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+ if currentMaximum is None or currentMaximum < compareWith:
+ currentMaximum = compareWith
+ currentIsStartTime = isStartTime
+
+ return currentMaximum, currentIsStartTime
+
+class ParameterFilter (FilterChildBase):
+ """
+ Limits a search to specific parameters.
+ """
+
+ def _match(self, property, access):
+
+ # We have to deal with the problem that the 'Native' form of a property
+ # will be missing the TZID parameter due to the conversion performed. Converting
+ # to non-native for the entire calendar object causes problems elsewhere, so its
+ # best to do it here for this one special case.
+ if self.filter_name == "TZID":
+ transformed = property.transformAllFromNative()
+ else:
+ transformed = False
+
+ # At least one property must match (or is-not-defined is set)
+ result = not self.defined
+ for parameterName in property.params().keys():
+ if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
+ result = self.defined
+ break
+
+ if transformed:
+ property.transformAllToNative()
+ return result
+
+class IsNotDefined (FilterBase):
+ """
+ Specifies that the named iCalendar item does not exist.
+ """
+
+ def match(self, component, access=None):
+ # Oddly, this needs always to return True so that it appears there is
+ # a match - but we then "negate" the result if is-not-defined is set.
+ # Actually this method should never be called as we special case the
+ # is-not-defined option.
+ return True
+
+class TextMatch (FilterBase):
+ """
+ Specifies a substring match on a property or parameter value.
+ (CalDAV-access-09, section 9.6.4)
+ """
+ def __init__(self, xml_element):
+
+ super(TextMatch, self).__init__(xml_element)
+
+ self.text = str(xml_element)
+ if "caseless" in xml_element.attributes:
+ caseless = xml_element.attributes["caseless"]
+ if caseless == "yes":
+ self.caseless = True
+ elif caseless == "no":
+ self.caseless = False
+ else:
+ self.caseless = True
+
+ if "negate-condition" in xml_element.attributes:
+ negate = xml_element.attributes["negate-condition"]
+ if negate == "yes":
+ self.negate = True
+ elif caseless == "no":
+ self.negate = False
+ else:
+ self.negate = False
+
+ def match(self, item, access):
+ """
+ Match the text for the item.
+ If the item is a property, then match the property value,
+ otherwise it may be a list of parameter values - try to match anyone of those
+ """
+ if item is None: return False
+
+ if isinstance(item, Property):
+ values = [item.value()]
+ else:
+ values = item
+
+ test = unicode(self.text, "utf-8")
+ if self.caseless:
+ test = test.lower()
+
+ def _textCompare(s):
+ if self.caseless:
+ if s.lower().find(test) != -1:
+ return True, not self.negate
+ else:
+ if s.find(test) != -1:
+ return True, not self.negate
+ return False, False
+
+ for value in values:
+ # NB Its possible that we have a text list value which appears as a Python list,
+ # so we need to check for that an iterate over the list.
+ if isinstance(value, list):
+ for subvalue in value:
+ matched, result = _textCompare(subvalue)
+ if matched:
+ return result
+ else:
+ matched, result = _textCompare(value)
+ if matched:
+ return result
+
+ return self.negate
+
+class TimeRange (FilterBase):
+ """
+ Specifies a time for testing components against.
+ """
+
+ def __init__(self, xml_element):
+
+ super(TimeRange, self).__init__(xml_element)
+
+ # One of start or end must be present
+ if "start" not in xml_element.attributes and "end" not in xml_element.attributes:
+ raise ValueError("One of 'start' or 'end' must be present in CALDAV:time-range")
+
+ self.start = parse_date_or_datetime(xml_element.attributes["start"]) if "start" in xml_element.attributes else None
+ self.end = parse_date_or_datetime(xml_element.attributes["end"]) if "end" in xml_element.attributes else None
+ self.tzinfo = None
+
+ def settzinfo(self, tzinfo):
+ """
+ Set the default timezone to use with this query.
+ @param tzinfo: a L{datetime.tzinfo} to use.
+ """
+
+ # Give tzinfo to any TimeRange we have
+ self.tzinfo = tzinfo
+
+ def valid(self, level=0):
+ """
+ Indicate whether the time-range is valid (must be date-time in UTC).
+
+ @return: True if valid, False otherwise
+ """
+
+ if self.start is not None and not isinstance(self.start, datetime.datetime):
+ log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
+ return False
+ if self.end is not None and not isinstance(self.end, datetime.datetime):
+ log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
+ return False
+ if self.start is not None and self.start.tzinfo != utc:
+ log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
+ return False
+ if self.end is not None and self.end.tzinfo != utc:
+ log.msg("end attribute in <time-range> is not UTC: %s" % (self.end,))
+ return False
+
+ # No other tests
+ return True
+
+ def match(self, property, access=None):
+ """
+ NB This is only called when doing a time-range match on a property.
+ """
+ if property is None:
+ return False
+ else:
+ return property.containsTimeRange(self.start, self.end, self.tzinfo)
+
+ def matchinstance(self, component, instances):
+ """
+ Test whether this time-range element causes a match to the specified component
+ using the specified set of instances to determine the expanded time ranges.
+ @param component: the L{Component} to test.
+ @param instances: the list of expanded instances.
+ @return: True if the time-range query matches, False otherwise.
+ """
+ if component is None:
+ return False
+
+ assert instances is not None or self.end is None, "Failure to expand instance for time-range filter: %r" % (self,)
+
+ # Special case open-ended unbounded
+ if instances is None:
+ if component.getRecurrenceIDUTC() is None:
+ return True
+ else:
+ # See if the overridden component's start is past the start
+ start, _ignore_end = component.getEffectiveStartEnd()
+ if start is None:
+ return True
+ else:
+ return start >= self.start
+
+ # Handle alarms as a special case
+ alarms = (component.name() == "VALARM")
+ if alarms:
+ testcomponent = component._parent
+ else:
+ testcomponent = component
+
+ for key in instances:
+ instance = instances[key]
+
+ # First make sure components match
+ if not testcomponent.same(instance.component):
+ continue
+
+ if alarms:
+ # Get all the alarm triggers for this instance and test each one
+ triggers = instance.getAlarmTriggers()
+ for trigger in triggers:
+ if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
+ return True
+ else:
+ # Regular instance overlap test
+ if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
+ return True
+
+ return False
Modified: CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/query/sqlgenerator.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -30,23 +30,26 @@
class sqlgenerator(object):
- FROM =" from "
- WHERE =" where "
- RESOURCEDB = "RESOURCE"
- TIMESPANDB = "TIMESPAN"
- NOTOP = "NOT "
- ANDOP = " AND "
- OROP = " OR "
- CONTAINSOP = " GLOB "
- NOTCONTAINSOP = " NOT GLOB "
- ISOP = " == "
- ISNOTOP = " != "
- INOP = " IN "
- NOTINOP = " NOT IN "
+ FROM =" from "
+ WHERE =" where "
+ RESOURCEDB = "RESOURCE"
+ TIMESPANDB = "TIMESPAN"
+ TRANSPARENCYDB = "TRANSPARENCY"
+ PERUSERDB = "PERUSER"
+ NOTOP = "NOT "
+ ANDOP = " AND "
+ OROP = " OR "
+ CONTAINSOP = " GLOB "
+ NOTCONTAINSOP = " NOT GLOB "
+ ISOP = " == "
+ ISNOTOP = " != "
+ INOP = " IN "
+ NOTINOP = " NOT IN "
- TIMESPANTEST = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s AND TIMESPAN.END > %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
- TIMESPANTEST_NOEND = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END > %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
- TIMESPANTEST_NOSTART = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
+ TIMESPANTEST = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s AND TIMESPAN.END > %s))"
+ TIMESPANTEST_NOEND = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END > %s))"
+ TIMESPANTEST_NOSTART = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s))"
+ TIMESPANTEST_TAIL_PIECE = " AND TIMESPAN.RESOURCEID == RESOURCE.RESOURCEID AND TIMESPAN.INSTANCEID == TRANSPARENCY.INSTANCEID"
def __init__(self, expr):
self.expression = expr
@@ -72,7 +75,7 @@
# Prefix with ' from ...' partial statement
select = self.FROM + self.RESOURCEDB
if self.usedtimespan:
- select += ", " + self.TIMESPANDB
+ select += ", %s, %s, %s" % (self.TIMESPANDB, self.TRANSPARENCYDB, self.PERUSERDB,)
select += self.sout.getvalue()
return select, self.arguments
@@ -134,6 +137,7 @@
arg1 = self.setArgument(expr.end)
arg2 = self.setArgument(expr.endfloat)
test = self.TIMESPANTEST_NOSTART % (arg1, arg2)
+ test += self.TIMESPANTEST_TAIL_PIECE
self.sout.write(test)
self.usedtimespan = True
Deleted: CalendarServer/trunk/twistedcaldav/query/test/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/__init__.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/query/test/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,19 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Tests for the twistedcaldav.query module.
-"""
Copied: CalendarServer/trunk/twistedcaldav/query/test/__init__.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/test/__init__.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for the twistedcaldav.query module.
+"""
Deleted: CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/test_calendarquery.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,99 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav import caldavxml
-from twistedcaldav.query import queryfilter
-from twistedcaldav.query.calendarquery import sqlcalendarquery
-import datetime
-import twistedcaldav.test.util
-
-class Tests(twistedcaldav.test.util.TestCase):
-
- def test_query(self):
-
- filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- *[caldavxml.ComponentFilter(
- *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
- **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
- )],
- **{"name":"VCALENDAR"}
- )
- )
- filter = queryfilter.Filter(filter)
-
- # A complete implementation of current DST rules for major US time zones.
-
- def first_sunday_on_or_after(dt):
- days_to_go = 6 - dt.weekday()
- if days_to_go:
- dt += datetime.timedelta(days_to_go)
- return dt
-
- # In the US, DST starts at 2am (standard time) on the first Sunday in April.
- DSTSTART = datetime.datetime(1, 4, 1, 2)
- # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
- # which is the first Sunday on or after Oct 25.
- DSTEND = datetime.datetime(1, 10, 25, 1)
-
- ZERO = datetime.timedelta(0)
- HOUR = datetime.timedelta(hours=1)
-
- class USTimeZone(datetime.tzinfo):
-
- def __init__(self, hours, reprname, stdname, dstname):
- self.stdoffset = datetime.timedelta(hours=hours)
- self.reprname = reprname
- self.stdname = stdname
- self.dstname = dstname
-
- def __repr__(self):
- return self.reprname
-
- def tzname(self, dt):
- if self.dst(dt):
- return self.dstname
- else:
- return self.stdname
-
- def utcoffset(self, dt):
- return self.stdoffset + self.dst(dt)
-
- def dst(self, dt):
- if dt is None or dt.tzinfo is None:
- # An exception may be sensible here, in one or both cases.
- # It depends on how you want to treat them. The default
- # fromutc() implementation (called by the default astimezone()
- # implementation) passes a datetime with dt.tzinfo is self.
- return ZERO
- assert dt.tzinfo is self
-
- # Find first Sunday in April & the last in October.
- start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
- end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
-
- # Can't compare naive to aware objects, so strip the timezone from
- # dt first.
- if start <= dt.replace(tzinfo=None) < end:
- return HOUR
- else:
- return ZERO
-
- Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
- filter.child.settzinfo(Eastern)
-
- print sqlcalendarquery(filter)
-
\ No newline at end of file
Copied: CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/test_calendarquery.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_calendarquery.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,99 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav import caldavxml
+from twistedcaldav.query import queryfilter
+from twistedcaldav.query.calendarquery import sqlcalendarquery
+import datetime
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+ def test_query(self):
+
+ filter = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ *[caldavxml.ComponentFilter(
+ *[caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"})],
+ **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+ )],
+ **{"name":"VCALENDAR"}
+ )
+ )
+ filter = queryfilter.Filter(filter)
+
+ # A complete implementation of current DST rules for major US time zones.
+
+ def first_sunday_on_or_after(dt):
+ days_to_go = 6 - dt.weekday()
+ if days_to_go:
+ dt += datetime.timedelta(days_to_go)
+ return dt
+
+ # In the US, DST starts at 2am (standard time) on the first Sunday in April.
+ DSTSTART = datetime.datetime(1, 4, 1, 2)
+ # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
+ # which is the first Sunday on or after Oct 25.
+ DSTEND = datetime.datetime(1, 10, 25, 1)
+
+ ZERO = datetime.timedelta(0)
+ HOUR = datetime.timedelta(hours=1)
+
+ class USTimeZone(datetime.tzinfo):
+
+ def __init__(self, hours, reprname, stdname, dstname):
+ self.stdoffset = datetime.timedelta(hours=hours)
+ self.reprname = reprname
+ self.stdname = stdname
+ self.dstname = dstname
+
+ def __repr__(self):
+ return self.reprname
+
+ def tzname(self, dt):
+ if self.dst(dt):
+ return self.dstname
+ else:
+ return self.stdname
+
+ def utcoffset(self, dt):
+ return self.stdoffset + self.dst(dt)
+
+ def dst(self, dt):
+ if dt is None or dt.tzinfo is None:
+ # An exception may be sensible here, in one or both cases.
+ # It depends on how you want to treat them. The default
+ # fromutc() implementation (called by the default astimezone()
+ # implementation) passes a datetime with dt.tzinfo is self.
+ return ZERO
+ assert dt.tzinfo is self
+
+ # Find first Sunday in April & the last in October.
+ start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
+ end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
+
+ # Can't compare naive to aware objects, so strip the timezone from
+ # dt first.
+ if start <= dt.replace(tzinfo=None) < end:
+ return HOUR
+ else:
+ return ZERO
+
+ Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
+ filter.child.settzinfo(Eastern)
+
+ print sqlcalendarquery(filter)
+
\ No newline at end of file
Deleted: CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/test_queryfilter.py 2010-04-07 19:28:58 UTC (rev 5440)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -1,77 +0,0 @@
-##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav import caldavxml
-from twistedcaldav.query import queryfilter
-import twistedcaldav.test.util
-
-class Tests(twistedcaldav.test.util.TestCase):
-
- def test_allQuery(self):
-
- xml_element = caldavxml.Filter(
- caldavxml.ComponentFilter(
- **{"name":"VCALENDAR"}
- )
- )
-
- queryfilter.Filter(xml_element)
-
- def test_simpleSummaryRangeQuery(self):
-
- xml_element = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- caldavxml.PropertyFilter(
- caldavxml.TextMatch.fromString("test"),
- **{"name":"SUMMARY",}
- ),
- **{"name":"VEVENT"}
- ),
- **{"name":"VCALENDAR"}
- )
- )
-
- queryfilter.Filter(xml_element)
-
- def test_simpleTimeRangeQuery(self):
-
- xml_element = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
- **{"name":"VEVENT"}
- ),
- **{"name":"VCALENDAR"}
- )
- )
-
- queryfilter.Filter(xml_element)
-
- def test_multipleTimeRangeQuery(self):
-
- xml_element = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
- **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
- ),
- **{"name":"VCALENDAR"}
- )
- )
-
- queryfilter.Filter(xml_element)
-
\ No newline at end of file
Copied: CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/test/test_queryfilter.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_queryfilter.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,77 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav import caldavxml
+from twistedcaldav.query import queryfilter
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+ def test_allQuery(self):
+
+ xml_element = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ **{"name":"VCALENDAR"}
+ )
+ )
+
+ queryfilter.Filter(xml_element)
+
+ def test_simpleSummaryRangeQuery(self):
+
+ xml_element = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ caldavxml.PropertyFilter(
+ caldavxml.TextMatch.fromString("test"),
+ **{"name":"SUMMARY",}
+ ),
+ **{"name":"VEVENT"}
+ ),
+ **{"name":"VCALENDAR"}
+ )
+ )
+
+ queryfilter.Filter(xml_element)
+
+ def test_simpleTimeRangeQuery(self):
+
+ xml_element = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
+ **{"name":"VEVENT"}
+ ),
+ **{"name":"VCALENDAR"}
+ )
+ )
+
+ queryfilter.Filter(xml_element)
+
+ def test_multipleTimeRangeQuery(self):
+
+ xml_element = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ caldavxml.TimeRange(**{"start":"20060605T160000Z", "end":"20060605T170000Z"}),
+ **{"name":("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
+ ),
+ **{"name":"VCALENDAR"}
+ )
+ )
+
+ queryfilter.Filter(xml_element)
+
\ No newline at end of file
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -61,14 +61,37 @@
from twistedcaldav.config import config
from twistedcaldav.customxml import TwistedCalendarAccessProperty
from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.extensions import DAVResource, DAVPrincipalResource
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
+ PropertyNotFoundError
from twistedcaldav.ical import Component
from twistedcaldav.ical import Component as iComponent
from twistedcaldav.ical import allowedComponents
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
+from twistedcaldav.sharing import SharedCollectionMixin
from twistedcaldav.vcard import Component as vComponent
+##
+# Sharing Conts
+##
+SHARE_ACCEPT_STATE_NEEDS_ACTION = "0"
+SHARE_ACCEPT_STATE_ACCEPTED = "1"
+SHARE_ACCEPT_STATE_DECLINED = "2"
+SHARE_ACCEPT_STATE_DELETED = "-1"
+
+shareAccpetStates = {}
+shareAccpetStates[SHARE_ACCEPT_STATE_NEEDS_ACTION] = "NEEDS-ACTION"
+shareAccpetStates[SHARE_ACCEPT_STATE_ACCEPTED] = "ACCEPTED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DECLINED] = "DECLINED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DELETED] = "DELETED"
+
+shareAcceptStatesByXML = {}
+shareAcceptStatesByXML["NEEDS-ACTION"] = customxml.InviteStatusNoResponse()
+shareAcceptStatesByXML["ACCEPTED"] = customxml.InviteStatusAccepted()
+shareAcceptStatesByXML["DECLINED"] = customxml.InviteStatusDeclined()
+shareAcceptStatesByXML["DELETED"] = customxml.InviteStatusDeleted()
+
class CalDAVComplianceMixIn(object):
def davComplianceClasses(self):
return (
@@ -77,7 +100,7 @@
)
-class CalDAVResource (CalDAVComplianceMixIn, DAVResource, LoggingMixIn):
+class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
"""
CalDAV resource.
@@ -137,15 +160,55 @@
##
liveProperties = DAVResource.liveProperties + (
- (dav_namespace, "owner"), # Private Events needs this but it is also OK to return empty
- (caldav_namespace, "supported-calendar-component-set"),
- (caldav_namespace, "supported-calendar-data" ),
+ davxml.Owner.qname(), # Private Events needs this but it is also OK to return empty
+ caldavxml.SupportedCalendarComponentSet.qname(),
+ caldavxml.SupportedCalendarData.qname(),
)
supportedCalendarComponentSet = caldavxml.SupportedCalendarComponentSet(
*[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
)
+ @classmethod
+ def enableSharing(clz, enable):
+ qname = (calendarserver_namespace, "invite" )
+ if enable and qname not in clz.liveProperties:
+ clz.liveProperties += (qname,)
+ elif not enable and qname in clz.liveProperties:
+ clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
+
+ def isShadowableProperty(self, qname):
+ """
+ Shadowable properties are ones on shared resources where a "default" exists until
+ a user overrides with their own value.
+ """
+ return qname in (
+ caldavxml.CalendarDescription.qname(),
+ caldavxml.CalendarTimeZone.qname(),
+ )
+
+ def isGlobalProperty(self, qname):
+ """
+ A global property is one that is the same for all users.
+ """
+ if qname in self.liveProperties:
+ if qname in (
+ davxml.DisplayName.qname(),
+ customxml.Invite.qname(),
+ ):
+ return False
+ else:
+ return True
+ elif qname in (
+ customxml.GETCTag.qname(),
+ caldavxml.MaxResourceSize.qname(),
+ caldavxml.MaxAttendeesPerInstance.qname(),
+ ):
+ return True
+ else:
+ return False
+
+ @inlineCallbacks
def hasProperty(self, property, request):
"""
Need to special case schedule-calendar-transp for backwards compatability.
@@ -156,8 +219,34 @@
else:
qname = property.qname()
+ isvirt = (yield self.isVirtualShare(request))
+ 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)):
+ ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
+ p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
+ returnValue(p)
+
+ res = (yield self._hasGlobalProperty(property, request))
+ returnValue(res)
+
+ def _hasGlobalProperty(self, property, request):
+ """
+ Need to special case schedule-calendar-transp for backwards compatability.
+ """
+
+ if type(property) is tuple:
+ qname = property
+ else:
+ qname = property.qname()
+
# Force calendar collections to always appear to have the property
- if qname == (caldav_namespace, "schedule-calendar-transp") and self.isCalendarCollection():
+ if qname == caldavxml.ScheduleCalendarTransp.qname() and self.isCalendarCollection():
return succeed(True)
else:
return super(CalDAVResource, self).hasProperty(property, request)
@@ -169,52 +258,75 @@
else:
qname = property.qname()
- namespace, name = qname
+ isvirt = (yield self.isVirtualShare(request))
+ 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)):
+ ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
+ p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
+ returnValue(p)
- if namespace == dav_namespace:
- if name == "owner":
- owner = (yield self.owner(request))
- returnValue(davxml.Owner(owner))
+ res = (yield self._readGlobalProperty(qname, property, request))
+ returnValue(res)
- elif namespace == caldav_namespace:
- if name == "supported-calendar-component-set":
- # CalDAV-access-09, section 5.2.3
- if self.hasDeadProperty(qname):
- returnValue(self.readDeadProperty(qname))
- returnValue(self.supportedCalendarComponentSet)
- elif name == "supported-calendar-data":
- # CalDAV-access-09, section 5.2.4
- returnValue(caldavxml.SupportedCalendarData(
- caldavxml.CalendarData(**{
- "content-type": "text/calendar",
- "version" : "2.0",
- }),
+ @inlineCallbacks
+ def _readGlobalProperty(self, qname, property, request):
+
+ if qname == davxml.Owner.qname():
+ owner = (yield self.owner(request))
+ returnValue(davxml.Owner(owner))
+
+ elif qname == caldavxml.SupportedCalendarComponentSet.qname():
+ # CalDAV-access-09, section 5.2.3
+ if self.hasDeadProperty(qname):
+ returnValue(self.readDeadProperty(qname))
+ returnValue(self.supportedCalendarComponentSet)
+
+ elif qname == caldavxml.SupportedCalendarData.qname():
+ # CalDAV-access-09, section 5.2.4
+ returnValue(caldavxml.SupportedCalendarData(
+ caldavxml.CalendarData(**{
+ "content-type": "text/calendar",
+ "version" : "2.0",
+ }),
+ ))
+
+ elif qname == caldavxml.MaxResourceSize.qname():
+ # CalDAV-access-15, section 5.2.5
+ if config.MaximumAttachmentSize:
+ returnValue(caldavxml.MaxResourceSize.fromString(
+ str(config.MaximumAttachmentSize)
))
- elif name == "max-resource-size":
- # CalDAV-access-15, section 5.2.5
- if config.MaximumAttachmentSize:
- returnValue(caldavxml.MaxResourceSize.fromString(
- str(config.MaximumAttachmentSize)
- ))
- elif name == "max-attendees-per-instance":
- # CalDAV-access-15, section 5.2.9
- if config.MaxAttendeesPerInstance:
- returnValue(caldavxml.MaxAttendeesPerInstance.fromString(
- str(config.MaxAttendeesPerInstance)
- ))
+ elif qname == caldavxml.MaxAttendeesPerInstance.qname():
+ # CalDAV-access-15, section 5.2.9
+ if config.MaxAttendeesPerInstance:
+ returnValue(caldavxml.MaxAttendeesPerInstance.fromString(
+ str(config.MaxAttendeesPerInstance)
+ ))
- elif name == "schedule-calendar-transp":
- # For backwards compatibility, if the property does not exist we need to create
- # it and default to the old free-busy-set value.
- if self.isCalendarCollection() and not self.hasDeadProperty(property):
- # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
- principal = (yield self.ownerPrincipal(request))
- fbset = (yield principal.calendarFreeBusyURIs(request))
- url = (yield self.canonicalURL(request))
- opaque = url in fbset
- self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
+ elif qname == caldavxml.ScheduleCalendarTransp.qname():
+ # For backwards compatibility, if the property does not exist we need to create
+ # it and default to the old free-busy-set value.
+ if self.isCalendarCollection() and not self.hasDeadProperty(property):
+ # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
+ principal = (yield self.resourceOwnerPrincipal(request))
+ fbset = (yield principal.calendarFreeBusyURIs(request))
+ url = (yield self.canonicalURL(request))
+ opaque = url in fbset
+ self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
+ elif qname == customxml.Invite.qname():
+ result = (yield self.inviteProperty(request))
+ returnValue(result)
+
result = (yield super(CalDAVResource, self).readProperty(property, request))
returnValue(result)
@@ -223,8 +335,21 @@
assert isinstance(property, davxml.WebDAVElement), (
"%r is not a WebDAVElement instance" % (property,)
)
+
+ # Per-user Dav props currently only apply to a sharee's copy of a calendar
+ isvirt = (yield self.isVirtualShare(request))
+ if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
+ yield self._preProcessWriteProperty(property, request)
+ ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
+ p = self.deadProperties().set(property, uid=ownerPrincipal.principalUID())
+ returnValue(p)
+
+ res = (yield self._writeGlobalProperty(property, request))
+ returnValue(res)
- if property.qname() == (caldav_namespace, "supported-calendar-component-set"):
+ @inlineCallbacks
+ def _preProcessWriteProperty(self, property, request):
+ if property.qname() == caldavxml.SupportedCalendarComponentSet.qname():
if not self.isPseudoCalendarCollection():
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
@@ -241,7 +366,7 @@
# server enforces what can be stored, however it need not actually
# exist so we cannot list it in liveProperties on this resource, since its
# its presence there means that hasProperty will always return True for it.
- elif property.qname() == (caldav_namespace, "calendar-timezone"):
+ elif property.qname() == caldavxml.CalendarTimeZone.qname():
if not self.isCalendarCollection():
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
@@ -254,7 +379,7 @@
description="Invalid property"
))
- elif property.qname() == (caldav_namespace, "schedule-calendar-transp"):
+ elif property.qname() == caldavxml.ScheduleCalendarTransp.qname():
if not self.isCalendarCollection():
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
@@ -262,7 +387,7 @@
))
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
- principal = (yield self.ownerPrincipal(request))
+ principal = (yield self.resourceOwnerPrincipal(request))
# Map owner to their inbox
inboxURL = principal.scheduleInboxURL()
@@ -271,15 +396,50 @@
myurl = (yield self.canonicalURL(request))
inbox.processFreeBusyCalendar(myurl, property.children[0] == caldavxml.Opaque())
- result = (yield super(CalDAVResource, self).writeProperty(property, request))
- returnValue(result)
+ @inlineCallbacks
+ def _writeGlobalProperty(self, property, request):
- def writeDeadProperty(self, property):
- val = super(CalDAVResource, self).writeDeadProperty(property)
+ yield self._preProcessWriteProperty(property, request)
- return val
+ if property.qname() == davxml.ResourceType.qname():
+ if self.isCalendarCollection():
+ sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
+ if not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Cannot create shared calendars on this server.",
+ ))
+ # Check if adding or removing share
+ shared = (yield self.isShared(request))
+ for child in property.children:
+ if child.qname() == davxml.Collection.qname():
+ break
+ else:
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Protected property %s may not be set." % (property.sname(),)
+ ))
+ for child in property.children:
+ if child.qname() == caldavxml.Calendar.qname():
+ break
+ else:
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Protected property %s may not be set." % (property.sname(),)
+ ))
+ sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
+ if not shared and sawShare:
+ # Owner is trying to share a collection
+ yield self.upgradeToShare(request)
+ elif shared and not sawShare:
+ # Remove share
+ yield self.downgradeFromShare(request)
+ returnValue(None)
+ result = (yield super(CalDAVResource, self).writeProperty(property, request))
+ returnValue(result)
+
##
# ACL
##
@@ -287,8 +447,13 @@
# FIXME: Perhaps this is better done in authorize() instead.
@inlineCallbacks
def accessControlList(self, request, *args, **kwargs):
- acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
+ isvirt = (yield self.isVirtualShare(request))
+ if isvirt:
+ acls = self.shareeAccessControlList()
+ else:
+ acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
+
# Look for private events access classification
if self.hasDeadProperty(TwistedCalendarAccessProperty):
access = self.readDeadProperty(TwistedCalendarAccessProperty)
@@ -333,31 +498,57 @@
returnValue(acls)
+ @inlineCallbacks
def owner(self, request):
"""
Return the DAV:owner property value (MUST be a DAV:href or None).
"""
- def _gotParent(parent):
- if parent and isinstance(parent, CalDAVResource):
- return parent.owner(request)
+ isVirt = (yield self.isVirtualShare(request))
+ if isVirt:
+ parent = (yield self.locateParent(request, self._share.hosturl))
+ else:
+ parent = (yield self.locateParent(request, request.urlForResource(self)))
+ if parent and isinstance(parent, CalDAVResource):
+ result = (yield parent.owner(request))
+ returnValue(result)
+ else:
+ returnValue(None)
- d = self.locateParent(request, request.urlForResource(self))
- d.addCallback(_gotParent)
- return d
-
+ @inlineCallbacks
def ownerPrincipal(self, request):
"""
Return the DAV:owner property value (MUST be a DAV:href or None).
"""
- def _gotParent(parent):
- if parent and isinstance(parent, CalDAVResource):
- return parent.ownerPrincipal(request)
+ isVirt = (yield self.isVirtualShare(request))
+ if isVirt:
+ parent = (yield self.locateParent(request, self._share.hosturl))
+ else:
+ parent = (yield self.locateParent(request, request.urlForResource(self)))
+ if parent and isinstance(parent, CalDAVResource):
+ result = (yield parent.ownerPrincipal(request))
+ returnValue(result)
+ else:
+ returnValue(None)
- d = self.locateParent(request, request.urlForResource(self))
- d.addCallback(_gotParent)
- return d
+ @inlineCallbacks
+ def resourceOwnerPrincipal(self, request):
+ """
+ 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 = (yield self.isVirtualShare(request))
+ if isVirt:
+ returnValue(self._shareePrincipal)
+ else:
+ parent = (yield self.locateParent(request, request.urlForResource(self)))
+ if parent and isinstance(parent, CalDAVResource):
+ result = (yield parent.resourceOwnerPrincipal(request))
+ returnValue(result)
+ else:
+ returnValue(None)
+
def isOwner(self, request, adminprincipals=False, readprincipals=False):
"""
Determine whether the DAV:owner of this resource matches the currently authorized principal
@@ -514,7 +705,7 @@
"""
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
- principal = (yield self.ownerPrincipal(request))
+ principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
if inboxURL:
inbox = (yield request.locateResource(inboxURL))
@@ -527,7 +718,7 @@
"""
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
- principal = (yield self.ownerPrincipal(request))
+ principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
if inboxURL:
(_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
@@ -556,7 +747,7 @@
assert self.isCalendarCollection()
# Not allowed to delete the default calendar
- principal = (yield self.ownerPrincipal(request))
+ principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
if inboxURL:
inbox = (yield request.locateResource(inboxURL))
@@ -588,6 +779,19 @@
except ValueError:
return None
+ @inlineCallbacks
+ def iCalendarForUser(self, request, name=None):
+
+ caldata = self.iCalendar(name)
+
+ accessUID = (yield self.resourceOwnerPrincipal(request))
+ if accessUID is None:
+ accessUID = ""
+ else:
+ accessUID = accessUID.principalUID()
+
+ returnValue(PerUserDataFilter(accessUID).filter(caldata))
+
def iCalendarRolledup(self, request):
"""
See L{ICalDAVResource.iCalendarRolledup}.
@@ -611,14 +815,6 @@
"""
return str(self.iCalendar(name))
- def iCalendarXML(self, name=None):
- """
- See L{ICalDAVResource.iCalendarXML}.
- This implementation returns an XML element constructed from the object
- returned by L{iCalendar} when given the same arguments.
- """
- return caldavxml.CalendarData.fromCalendar(self.iCalendar(name))
-
def iCalendarAddressDoNormalization(self, ical):
"""
Normalize calendar user addresses in the supplied iCalendar object into their
@@ -770,8 +966,11 @@
lastpath = path.split("/")[-1]
parent = (yield request.locateResource(parentForURL(myurl)))
- canonical_parent = (yield parent.canonicalURL(request))
- self._canonical_url = joinURL(canonical_parent, lastpath)
+ if parent:
+ canonical_parent = (yield parent.canonicalURL(request))
+ self._canonical_url = joinURL(canonical_parent, lastpath)
+ else:
+ self._canonical_url = myurl
returnValue(self._canonical_url)
@@ -789,7 +988,7 @@
"""
Quota root only ever set on calendar homes.
"""
- return None
+ return None
class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
"""
@@ -874,6 +1073,14 @@
clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
@classmethod
+ def enableSharing(clz, enable):
+ qname = (calendarserver_namespace, "notification-URL" )
+ if enable and qname not in clz.liveProperties:
+ clz.liveProperties += (qname,)
+ elif not enable and qname in clz.liveProperties:
+ clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
+
+ @classmethod
def enableAddressBooks(clz, enable):
qname = (carddav_namespace, "addressbook-home-set" )
if enable and qname not in clz.liveProperties:
@@ -929,6 +1136,13 @@
else:
returnValue(customxml.DropBoxHomeURL(davxml.HRef(url)))
+ elif name == "notification-URL" and config.Sharing.Enabled:
+ url = yield self.notificationURL()
+ if url is None:
+ returnValue(None)
+ else:
+ returnValue(customxml.NotificationURL(davxml.HRef(url)))
+
elif name == "calendar-proxy-read-for":
results = (yield self.proxyFor(False))
returnValue(customxml.CalendarProxyReadFor(
@@ -1026,6 +1240,13 @@
else:
return None
+ def notificationURL(self, request=None):
+ if self.hasDeadProperty((calendarserver_namespace, "notification-URL")):
+ notification = self.readDeadProperty((calendarserver_namespace, "notification-URL"))
+ return succeed(str(notification.children[0]))
+ else:
+ return succeed(None)
+
def addressBookHomeURLs(self):
if self.hasDeadProperty((carddav_namespace, "addressbook-home-set")):
home_set = self.readDeadProperty((carddav_namespace, "addressbook-home-set"))
@@ -1067,8 +1288,8 @@
self.parent = parent
- def resourceType(self):
- return davxml.ResourceType.searchaddressbook #@UndefinedVariable
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.searchaddressbook)
def renderHTTP(self, request):
return RedirectResponse(request.unparseURL(path="/directory/"))
@@ -1088,8 +1309,8 @@
self.parent = parent
- def resourceType(self):
- return davxml.ResourceType.searchalladdressbook #@UndefinedVariable
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.searchalladdressbook)
def renderHTTP(self, request):
Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/schedule.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -26,7 +26,7 @@
from twext.web2.dav.http import ErrorResponse
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twext.web2 import responsecode
from twext.web2.dav import davxml
from twext.web2.dav.util import joinURL, normalizeURL
@@ -87,8 +87,8 @@
(caldav_namespace, "schedule-default-calendar-URL"),
)
- def resourceType(self):
- return davxml.ResourceType.scheduleInbox
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.scheduleInbox)
def defaultAccessControlList(self):
@@ -253,8 +253,8 @@
else:
return super(ScheduleOutboxResource, self).defaultAccessControlList()
- def resourceType(self):
- return davxml.ResourceType.scheduleOutbox
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.scheduleOutbox)
@inlineCallbacks
def http_POST(self, request):
@@ -310,8 +310,8 @@
),
)
- def resourceType(self):
- return davxml.ResourceType.ischeduleinbox
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.ischeduleinbox)
def isCollection(self):
return False
Modified: CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/caldav.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/scheduling/caldav.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -249,6 +249,8 @@
# Now process free-busy set calendars
matchtotal = 0
for calendarResourceURL in fbset:
+ if not calendarResourceURL.endswith('/'):
+ calendarResourceURL += '/'
calendarResource = (yield self.scheduler.request.locateResource(calendarResourceURL))
if calendarResource is None or not calendarResource.exists() or not isCalendarCollectionResource(calendarResource):
# We will ignore missing calendars. If the recipient has failed to
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -71,7 +71,8 @@
self.internal_request = internal_request
existing_resource = resource.exists()
- existing_type = "schedule" if self.checkSchedulingObjectResource(resource) else "calendar"
+ is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+ existing_type = "schedule" if is_scheduling_object else "calendar"
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
if existing_type == "calendar":
@@ -88,7 +89,7 @@
# Also make sure that we return the new calendar being written rather than the old one
# when the implicit action is executed
self.return_calendar = calendar
- self.calendar = resource.iCalendar()
+ self.calendar = (yield resource.iCalendarForUser(request))
yield self.checkImplicitState()
# Attendees are not allowed to overwrite one type with another
@@ -108,8 +109,8 @@
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
dest_exists = destresource.exists()
- dest_is_implicit = self.checkSchedulingObjectResource(destresource)
- src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
+ dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
+ src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
if srccal and destcal:
if src_is_implicit and dest_exists or dest_is_implicit:
@@ -138,8 +139,8 @@
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
- dest_is_implicit = self.checkSchedulingObjectResource(destresource)
- src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
+ dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
+ src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
if srccal and destcal:
if src_is_implicit or dest_is_implicit:
@@ -167,11 +168,13 @@
yield self.checkImplicitState()
- resource_type = "schedule" if self.checkSchedulingObjectResource(resource) else "calendar"
+ is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+ resource_type = "schedule" if is_scheduling_object else "calendar"
self.action = "remove" if resource_type == "schedule" else "none"
returnValue((self.action != "none", False,))
+ @inlineCallbacks
def checkSchedulingObjectResource(self, resource):
if resource and resource.exists():
@@ -180,24 +183,24 @@
except HTTPError:
implicit = None
if implicit is not None:
- return implicit != "false"
+ returnValue(implicit != "false")
else:
- calendar = resource.iCalendar()
+ calendar = (yield resource.iCalendarForUser(self.request))
# Get the ORGANIZER and verify it is the same for all components
try:
organizer = calendar.validOrganizerForScheduling()
except ValueError:
# We have different ORGANIZERs in the same iCalendar object - this is an error
- return False
+ returnValue(False)
organizerPrincipal = resource.principalForCalendarUserAddress(organizer) if organizer else None
resource.writeDeadProperty(TwistedSchedulingObjectResource("true" if organizerPrincipal != None else "false"))
log.debug("Implicit - checked scheduling object resource state for UID: '%s', result: %s" % (
calendar.resourceUID(),
"true" if organizerPrincipal != None else "false",
))
- return organizerPrincipal != None
+ returnValue(organizerPrincipal != None)
- return False
+ returnValue(False)
@inlineCallbacks
def checkImplicitState(self):
@@ -379,7 +382,7 @@
returnValue(None)
# Get owner's calendar-home
- calendar_owner_principal = (yield self.resource.ownerPrincipal(self.request))
+ calendar_owner_principal = (yield self.resource.resourceOwnerPrincipal(self.request))
calendar_home = calendar_owner_principal.calendarHome()
check_parent_uri = parentForURL(check_uri)[:-1] if check_uri else None
@@ -397,7 +400,8 @@
child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
if child == check_resource:
returnValue(True)
- matched_type = "schedule" if self.checkSchedulingObjectResource(child) else "calendar"
+ is_scheduling_object = (yield self.checkSchedulingObjectResource(child))
+ matched_type = "schedule" if is_scheduling_object else "calendar"
if (
collection_uri != check_parent_uri and
(type == "schedule" or matched_type == "schedule")
@@ -494,7 +498,7 @@
elif self.action == "modify":
# Read in existing data
- self.oldcalendar = self.resource.iCalendar()
+ self.oldcalendar = (yield self.resource.iCalendarForUser(self.request))
# Significant change
no_change, self.changed_rids, reinvites, recurrence_reschedule = self.isOrganizerChangeInsignificant()
@@ -780,7 +784,7 @@
else:
# Make sure ORGANIZER is not changed
if self.resource.exists():
- self.oldcalendar = self.resource.iCalendar()
+ self.oldcalendar = (yield self.resource.iCalendarForUser(self.request))
oldOrganizer = self.oldcalendar.getOrganizer()
newOrganizer = self.calendar.getOrganizer()
if oldOrganizer != newOrganizer:
@@ -883,7 +887,7 @@
self.organizer_calendar = None
calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.organizerPrincipal, self.uid))
if calendar_resource:
- self.organizer_calendar = calendar_resource.iCalendar()
+ self.organizer_calendar = (yield calendar_resource.iCalendarForUser(self.request))
elif isinstance(self.organizerAddress, PartitionedCalendarUser):
# For partitioning where the organizer is on a different node, we will assume that the attendee's copy
# of the event is up to date and "authoritative". So we pretend that is the organizer copy
Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -667,12 +667,15 @@
Remove properties and parameters that should not be sent in an iTIP message
"""
+ # All X- components go away
+ itip.removeXComponents()
+
# Alarms
itip.removeAlarms()
# Top-level properties - remove all X-
itip.removeXProperties(do_subcomponents=False)
-
+
# Component properties - remove all X- except for those specified
if not reply:
# Organizer properties that need to go to the Attendees
Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -18,7 +18,7 @@
import time
from hashlib import md5
-from vobject.icalendar import utc
+from vobject.icalendar import dateTimeToString, utc
from twext.python.log import Logger
@@ -136,7 +136,7 @@
self.recipient_calendar_name = None
calendar_resource, resource_name, calendar_collection, calendar_collection_uri = (yield getCalendarObjectForPrincipals(self.request, self.recipient.principal, self.uid))
if calendar_resource:
- self.recipient_calendar = calendar_resource.iCalendar()
+ self.recipient_calendar = (yield calendar_resource.iCalendarForUser(self.request))
self.recipient_calendar_collection = calendar_collection
self.recipient_calendar_collection_uri = calendar_collection_uri
self.recipient_calendar_name = resource_name
@@ -504,9 +504,10 @@
dt = dt.replace(tzinfo=tzinfo).astimezone(utc)
return dt
- tr = caldavxml.TimeRange(start="20000101", end="20000101")
- tr.start = makeTimedUTC(instance.start)
- tr.end = makeTimedUTC(instance.end)
+ tr = caldavxml.TimeRange(
+ start=dateTimeToString(makeTimedUTC(instance.start)),
+ end=dateTimeToString(makeTimedUTC(instance.end)),
+ )
yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
@@ -708,7 +709,7 @@
calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.originator.principal, self.uid))
if not calendar_resource:
raise ImplicitProcessorException("5.1;Service unavailable")
- originator_calendar = calendar_resource.iCalendar()
+ originator_calendar = (yield calendar_resource.iCalendarForUser(self.request))
# Get attendee's view of that
originator_calendar.attendeesView((self.recipient.cuaddr,))
Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -16,6 +16,7 @@
from twisted.internet.defer import inlineCallbacks, succeed, returnValue
from twistedcaldav.method import report_common
+from twext.web2.dav.util import joinURL
@inlineCallbacks
def getCalendarObjectForPrincipals(request, principal, uid):
@@ -43,7 +44,10 @@
def queryCalendarCollection(collection, uri):
rname = collection.index().resourceNameForUID(uid)
if rname:
- result["resource"] = collection.getChild(rname)
+ resource = collection.getChild(rname)
+ request._rememberResource(resource, joinURL(uri, rname))
+
+ result["resource"] = resource
result["resource_name"] = rname
result["calendar_collection"] = collection
result["calendar_collection_uri"] = uri
Copied: CalendarServer/trunk/twistedcaldav/sharedcalendar.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharedcalendar.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcalendar.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sharedcalendar.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,85 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.python.log import LoggingMixIn
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav.extensions import DAVResource
+from twistedcaldav.resource import CalDAVComplianceMixIn
+from twistedcaldav.sharing import SharedCollectionMixin
+
+__all__ = [
+ "SharedCalendarResource",
+]
+
+"""
+Sharing behavior
+"""
+
+class SharedCalendarResource(CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
+ """
+ This is similar to a WrapperResource except that we locate our shared calendar resource dynamically.
+ """
+
+ def __init__(self, parent, share):
+ self.parent = parent
+ self.share = share
+ super(SharedCalendarResource, self).__init__(self.parent.principalCollections())
+
+ @inlineCallbacks
+ def hostedResource(self, request):
+
+ if not hasattr(self, "_hostedResource"):
+ self._hostedResource = (yield request.locateResource(self.share.hosturl))
+ ownerPrincipal = (yield self.parent.ownerPrincipal(request))
+ self._hostedResource.setVirtualShare(ownerPrincipal, self.share)
+ returnValue(self._hostedResource)
+
+ def isCollection(self):
+ return True
+
+ def locateChild(self, request, segments):
+
+ def _defer(result):
+ return (result, segments)
+ d = self.hostedResource(request)
+ d.addCallback(_defer)
+ return d
+
+ def renderHTTP(self, request):
+ return self.hostedResource(request)
+
+ def getChild(self, name):
+ return self._hostedResource.getChild(name)
+
+ @inlineCallbacks
+ def hasProperty(self, property, request):
+ hosted = (yield self.hostedResource(request))
+ result = (yield hosted.hasProperty(property, request))
+ returnValue(result)
+
+ @inlineCallbacks
+ def readProperty(self, property, request):
+ hosted = (yield self.hostedResource(request))
+ result = (yield hosted.readProperty(property, request))
+ returnValue(result)
+
+ @inlineCallbacks
+ def writeProperty(self, property, request):
+ hosted = (yield self.hostedResource(request))
+ result = (yield hosted.writeProperty(property, request))
+ returnValue(result)
Copied: CalendarServer/trunk/twistedcaldav/sharing.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,1091 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+ "SharedCollectionMixin",
+]
+
+from twext.python.log import LoggingMixIn
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav.util import allDataFromStream, joinURL
+from twext.web2.http import HTTPError, Response, StatusResponse, XMLResponse
+from twisted.internet.defer import succeed, inlineCallbacks, DeferredList,\
+ returnValue
+from twistedcaldav import customxml, caldavxml
+from twistedcaldav.config import config
+from twistedcaldav.customxml import SharedCalendar
+from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+from uuid import uuid4
+from vobject.icalendar import dateTimeToString, utc
+import datetime
+import os
+import types
+
+"""
+Sharing behavior
+"""
+
+class SharedCollectionMixin(object):
+
+ def invitesDB(self):
+
+ if not hasattr(self, "_invitesDB"):
+ self._invitesDB = InvitesDatabase(self)
+ return self._invitesDB
+
+ def inviteProperty(self, request):
+
+ # Build the CS:invite property from our DB
+ def sharedOK(isShared):
+ if config.Sharing.Enabled and isShared:
+ self.validateInvites()
+ return customxml.Invite(
+ *[record.makePropertyElement() for record in self.invitesDB().allRecords()]
+ )
+ else:
+ return None
+ return self.isShared(request).addCallback(sharedOK)
+
+ @inlineCallbacks
+ def upgradeToShare(self, request):
+ """ Upgrade this collection to a shared state """
+
+ # For calendars we only allow upgrades is shared-scheduling is on
+ if request.method not in ("MKCALENDAR", "MKCOL") and self.isCalendarCollection() and \
+ not config.Sharing.Calendars.AllowScheduling and len(self.listChildren()) != 0:
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
+
+ # Change resourcetype
+ rtype = (yield self.resourceType(request))
+ rtype = davxml.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
+ self.writeDeadProperty(rtype)
+
+ # Create invites database
+ self.invitesDB().create()
+
+ returnValue(True)
+
+ @inlineCallbacks
+ def downgradeFromShare(self, request):
+
+ # Change resource type (note this might be called after deleting a resource
+ # so we have to cope with that)
+ rtype = (yield self.resourceType(request))
+ rtype = davxml.ResourceType(*([child for child in rtype.children if child != customxml.SharedOwner()]))
+ self.writeDeadProperty(rtype)
+
+ # Remove all invitees
+ records = self.invitesDB().allRecords()
+ yield self.uninviteUserToShare([record.userid for record in records], None, request)
+
+ # Remove invites database
+ self.invitesDB().remove()
+ delattr(self, "_invitesDB")
+
+ returnValue(True)
+
+ def removeUserFromInvite(self, userid, request):
+ """ Remove a user from this shared calendar """
+ self.invitesDB().removeRecordForUserID(userid)
+
+ return succeed(True)
+
+ @inlineCallbacks
+ def changeUserInviteState(self, request, inviteUID, userid, state, summary=None):
+
+ shared = (yield self.isShared(request))
+ if not shared:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "invalid share",
+ ))
+
+ record = self.invitesDB().recordForInviteUID(inviteUID)
+ if record is None or record.userid != userid:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "invalid invitation uid: %s" % (inviteUID,),
+ ))
+
+ # 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
+ self.invitesDB().addOrUpdateRecord(record)
+
+ def isShared(self, request):
+ """ Return True if this is an owner shared calendar collection """
+ return succeed(self.isSpecialCollection(customxml.SharedOwner))
+
+ def setVirtualShare(self, shareePrincipal, share):
+ self._isVirtualShare = True
+ self._shareePrincipal = shareePrincipal
+ self._share = share
+
+ def isVirtualShare(self, request):
+ """ Return True if this is a shared calendar collection """
+ return succeed(hasattr(self, "_isVirtualShare"))
+
+ def removeVirtualShare(self, request):
+ """ Return True if this is a shared calendar collection """
+
+ # Remove from sharee's calendar home
+ shareeHome = self._shareePrincipal.calendarHome()
+ return shareeHome.removeShare(request, self._share)
+
+ @inlineCallbacks
+ def resourceType(self, request):
+
+ rtype = (yield super(SharedCollectionMixin, self).resourceType(request))
+ isVirt = (yield self.isVirtualShare(request))
+ if isVirt:
+ rtype = davxml.ResourceType(
+ *(
+ tuple([child for child in rtype.children if child.qname() != customxml.SharedOwner.qname()]) +
+ (customxml.Shared(),)
+ )
+ )
+ returnValue(rtype)
+
+ def sharedResourceType(self):
+ """
+ Return the DAV:resourcetype stripped of any shared elements.
+ """
+
+ if self.isCalendarCollection():
+ return "calendar"
+ elif self.isAddressBookCollection():
+ return "addressbook"
+ else:
+ return ""
+
+ def shareeAccessControlList(self):
+
+ assert self._isVirtualShare, "Only call this fort a virtual share"
+
+ # Get the invite for this sharee
+ invite = self.invitesDB().recordForInviteUID(self._share.inviteuid)
+ if invite is None:
+ return davxml.ACL()
+
+ userprivs = [
+ ]
+ if invite.access in ("read-only", "read-write", "read-write-schedule",):
+ userprivs.append(davxml.Privilege(davxml.Read()))
+ userprivs.append(davxml.Privilege(davxml.ReadACL()))
+ userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
+ if invite.access in ("read-only",):
+ userprivs.append(davxml.Privilege(davxml.WriteProperties()))
+ if invite.access in ("read-write", "read-write-schedule",):
+ userprivs.append(davxml.Privilege(davxml.Write()))
+ proxyprivs = list(userprivs)
+ proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
+
+ aces = (
+ # Inheritable specific access for the resource's associated principal.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(self._shareePrincipal.principalURL())),
+ davxml.Grant(*userprivs),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ # Inheritable CALDAV:read-free-busy access for authenticated users.
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ # Give read access to config.ReadPrincipals
+ aces += config.ReadACEs
+
+ # Give all access to config.AdminPrincipals
+ aces += config.AdminACEs
+
+ if config.EnableProxyPrincipals:
+ aces += (
+ # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-read/"))),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-write/"))),
+ davxml.Grant(
+ davxml.Privilege(*proxyprivs),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ return davxml.ACL(*aces)
+
+ def validUserIDForShare(self, userid):
+ """
+ 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 urn:uuid).
+
+ @param userid: the userid to test
+ @type userid: C{str}
+
+ @return: C{str} of normalized userid or C{None} if
+ userid is not allowed.
+ """
+
+ # First try to resolve as a principal
+ principal = self.principalForCalendarUserAddress(userid)
+ if principal:
+ return principal.principalURL()
+
+ # 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
+ else:
+ return None
+
+ def validateInvites(self):
+ """
+ Make sure each userid in an invite is valid - if not re-write status.
+ """
+
+ records = self.invitesDB().allRecords()
+ for record in records:
+ if self.validUserIDForShare(record.userid) is None and record.state != "INVALID":
+ record.state = "INVALID"
+ self.invitesDB().addOrUpdateRecord(record)
+
+ def getInviteUsers(self, request):
+ return succeed(True)
+
+ def sendNotificationOnChange(self, icalendarComponent, request, state="added"):
+ """ Possibly send a push and or email notification on a change to a resource in a shared collection """
+ return succeed(True)
+
+ def inviteUserToShare(self, userid, ace, summary, request, commonName="", shareName="", add=True):
+ """ Send out in invite first, and then add this user to the share list
+ @param userid:
+ @param ace: Must be one of customxml.ReadWriteAccess or customxml.ReadAccess
+ """
+
+ # Check for valid userid first
+ userid = self.validUserIDForShare(userid)
+ if userid is None:
+ return succeed(False)
+
+ # TODO: Check if this collection is shared, and error out if it isn't
+ if type(userid) is not list:
+ userid = [userid]
+ if type(commonName) is not list:
+ commonName = [commonName]
+ if type(shareName) is not list:
+ shareName = [shareName]
+
+ dl = [self.inviteSingleUserToShare(user, ace, summary, request, cn=cn, sn=sn) for user, cn, sn in zip(userid, commonName, shareName)]
+ return DeferredList(dl).addCallback(lambda _:True)
+
+ def uninviteUserToShare(self, userid, ace, request):
+ """ Send out in uninvite first, and then remove this user from the share list."""
+
+ # Do not validate the userid - we want to allow invalid users to be removed because they
+ # may have been valid when added, but no longer valid now. Clients should be able to clear out
+ # anything known to be invalid.
+
+ # TODO: Check if this collection is shared, and error out if it isn't
+ if type(userid) is not list:
+ userid = [userid]
+ return DeferredList([self.uninviteSingleUserFromShare(user, ace, request) for user in userid]).addCallback(lambda _:True)
+
+ def inviteUserUpdateToShare(self, userid, aceOLD, aceNEW, summary, request, commonName="", shareName=""):
+
+ # Check for valid userid first
+ userid = self.validUserIDForShare(userid)
+ if userid is None:
+ return succeed(False)
+
+ if type(userid) is not list:
+ userid = [userid]
+ if type(commonName) is not list:
+ commonName = [commonName]
+ if type(shareName) is not list:
+ shareName = [shareName]
+ dl = [self.inviteSingleUserUpdateToShare(user, aceOLD, aceNEW, summary, request, commonName=cn, shareName=sn) for user, cn, sn in zip(userid, commonName, shareName)]
+ return DeferredList(dl).addCallback(lambda _:True)
+
+ @inlineCallbacks
+ def inviteSingleUserToShare(self, userid, ace, summary, request, cn="", sn=""):
+
+ # Look for existing invite and update its fields or create new one
+ record = self.invitesDB().recordForUserID(userid)
+ if record:
+ record.access = inviteAccessMapFromXML[type(ace)]
+ record.summary = summary
+ else:
+ record = Invite(str(uuid4()), userid, inviteAccessMapFromXML[type(ace)], "NEEDS-ACTION", summary)
+
+ # Send invite
+ yield self.sendInvite(record, request)
+
+ # Add to database
+ self.invitesDB().addOrUpdateRecord(record)
+
+ returnValue(True)
+
+ @inlineCallbacks
+ def uninviteSingleUserFromShare(self, userid, aces, request):
+
+ newuserid = self.validUserIDForShare(userid)
+ if newuserid:
+ userid = newuserid
+
+ # Cancel invites
+ record = self.invitesDB().recordForUserID(userid)
+
+ # Remove any shared calendar
+ sharee = self.principalForCalendarUserAddress(record.userid)
+ if sharee is None:
+ raise ValueError("sharee is None but userid was valid before")
+ shareeHome = sharee.calendarHome()
+ yield shareeHome.removeShareByUID(request, record.inviteuid)
+
+ # 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)
+
+ # Remove from database
+ self.invitesDB().removeRecordForUserID(userid)
+
+ returnValue(True)
+
+ def inviteSingleUserUpdateToShare(self, userid, acesOLD, aceNEW, summary, request, commonName="", shareName=""):
+
+ # Just update existing
+ return self.inviteSingleUserToShare(userid, aceNEW, summary, request, commonName, shareName)
+
+ @inlineCallbacks
+ def sendInvite(self, record, request):
+
+ owner = (yield self.ownerPrincipal(request))
+ owner = owner.principalURL()
+ hosturl = (yield self.canonicalURL(request))
+
+ # Locate notifications collection for user
+ sharee = self.principalForCalendarUserAddress(record.userid)
+ if sharee is None:
+ raise ValueError("sharee is None but userid was valid before")
+ notifications = (yield request.locateResource(sharee.notificationURL()))
+
+ # Look for existing notification
+ oldnotification = (yield notifications.getNotifictionMessageByUID(request, record.inviteuid))
+ if oldnotification:
+ # TODO: rollup changes?
+ pass
+
+ # Generate invite XML
+ typeAttr = {'shared-type':self.sharedResourceType()}
+ xmltype = customxml.InviteNotification(**typeAttr)
+ xmldata = customxml.Notification(
+ customxml.DTStamp.fromString(dateTimeToString(datetime.datetime.now(tz=utc))),
+ customxml.InviteNotification(
+ customxml.UID.fromString(record.inviteuid),
+ davxml.HRef.fromString(record.userid),
+ inviteStatusMapToXML[record.state](),
+ customxml.InviteAccess(inviteAccessMapToXML[record.access]()),
+ customxml.HostURL(
+ davxml.HRef.fromString(hosturl),
+ ),
+ customxml.Organizer(
+ davxml.HRef.fromString(owner),
+ ),
+ customxml.InviteSummary.fromString(record.summary),
+ **typeAttr
+ ),
+ ).toxml()
+
+ # Add to collections
+ yield notifications.addNotification(request, record.inviteuid, xmltype, xmldata)
+
+ @inlineCallbacks
+ def removeInvite(self, record, request):
+
+ # Locate notifications collection for user
+ sharee = self.principalForCalendarUserAddress(record.userid)
+ if sharee is None:
+ raise ValueError("sharee is None but userid was valid before")
+ notifications = (yield request.locateResource(sharee.notificationURL()))
+
+ # Add to collections
+ yield notifications.deleteNotifictionMessageByUID(request, record.inviteuid)
+
+ def xmlPOSTNoAuth(self, encoding, request):
+ def _handleErrorResponse(error):
+ if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
+ return error.value.response
+ return Response(code=responsecode.BAD_REQUEST)
+
+ def _handleInvite(invitedoc):
+ def _handleInviteSet(inviteset):
+ userid = None
+ access = None
+ summary = None
+ for item in inviteset.children:
+ if isinstance(item, davxml.HRef):
+ userid = str(item)
+ continue
+ if isinstance(item, customxml.InviteSummary):
+ summary = str(item)
+ continue
+ if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
+ access = item
+ continue
+ if userid and access and summary:
+ return (userid, access, summary)
+ else:
+ if userid is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "missing href: %s" % (inviteset,),
+ ))
+ if access is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "missing access: %s" % (inviteset,),
+ ))
+ if summary is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "missing summary: %s" % (inviteset,),
+ ))
+
+ def _handleInviteRemove(inviteremove):
+ userid = None
+ access = []
+ for item in inviteremove.children:
+ if isinstance(item, davxml.HRef):
+ userid = str(item)
+ continue
+ if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
+ access.append(item)
+ continue
+ if userid is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "missing href: %s" % (inviteremove,),
+ ))
+ if len(access) == 0:
+ access = None
+ else:
+ access = set(access)
+ return (userid, access)
+
+ def _autoShare(isShared, request):
+ if not isShared:
+ if not self.isCalendarCollection() or config.Sharing.Calendars.AllowScheduling or len(self.listChildren()) == 0:
+ return self.upgradeToShare(request)
+ else:
+ return succeed(True)
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
+
+ @inlineCallbacks
+ def _processInviteDoc(_, request):
+ setDict, removeDict, updateinviteDict = {}, {}, {}
+ for item in invitedoc.children:
+ if isinstance(item, customxml.InviteSet):
+ userid, access, summary = _handleInviteSet(item)
+ setDict[userid] = (access, summary)
+ elif isinstance(item, customxml.InviteRemove):
+ userid, access = _handleInviteRemove(item)
+ removeDict[userid] = access
+
+ # Special case removing and adding the same user and treat that as an add
+ okusers = set()
+ badusers = set()
+ sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
+ for u in sameUseridInRemoveAndSet:
+ removeACL = removeDict[u]
+ newACL, summary = setDict[u]
+ updateinviteDict[u] = (removeACL, newACL, summary)
+ del removeDict[u]
+ del setDict[u]
+ for userid, access in removeDict.iteritems():
+ result = (yield self.uninviteUserToShare(userid, access, request))
+ (okusers if result else badusers).add(userid)
+ for userid, (access, summary) in setDict.iteritems():
+ result = (yield self.inviteUserToShare(userid, access, summary, request))
+ (okusers if result else badusers).add(userid)
+ for userid, (removeACL, newACL, summary) in updateinviteDict.iteritems():
+ result = (yield self.inviteUserUpdateToShare(userid, removeACL, newACL, summary, request))
+ (okusers if result else badusers).add(userid)
+
+ # Do a final validation of the entire set of invites
+ self.validateInvites()
+
+ # Create the multistatus response - only needed if some are bad
+ if badusers:
+ xml_responses = []
+ xml_responses.extend([
+ davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.OK))
+ for userid in sorted(okusers)
+ ])
+ xml_responses.extend([
+ davxml.StatusResponse(davxml.HRef(userid), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))
+ for userid in sorted(badusers)
+ ])
+
+ #
+ # Return response
+ #
+ returnValue(MultiStatusResponse(xml_responses))
+ else:
+ returnValue(responsecode.OK)
+
+
+ return self.isShared(request).addCallback(_autoShare, request).addCallback(_processInviteDoc, request)
+
+ def _getData(data):
+ try:
+ doc = davxml.WebDAVDocument.fromString(data)
+ except ValueError, e:
+ self.log_error("Error parsing doc (%s) Doc:\n %s" % (str(e), data,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request")))
+
+ root = doc.root_element
+ xmlDocHanders = {
+ customxml.InviteShare: _handleInvite,
+ }
+ if type(root) in xmlDocHanders:
+ return xmlDocHanders[type(root)](root).addErrback(_handleErrorResponse)
+ else:
+ self.log_error("Unsupported XML (%s)" % (root,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request")))
+
+ return allDataFromStream(request.stream).addCallback(_getData)
+
+ def xmlPOSTPreconditions(self, _, request):
+ if request.headers.hasHeader("Content-Type"):
+ mimetype = request.headers.getHeader("Content-Type")
+ if mimetype.mediaType in ("application", "text",) and mimetype.mediaSubtype == "xml":
+ encoding = mimetype.params["charset"] if "charset" in mimetype.params else "utf8"
+ return succeed(encoding)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request")))
+
+ def xmlPOSTAuth(self, request):
+ d = self.authorize(request, (davxml.Read(), davxml.Write()))
+ d.addCallback(self.xmlPOSTPreconditions, request)
+ d.addCallback(self.xmlPOSTNoAuth, request)
+ return d
+
+ def http_POST(self, request):
+ if self.isCollection():
+ contentType = request.headers.getHeader("content-type")
+ if contentType:
+ contentType = (contentType.mediaType, contentType.mediaSubtype)
+ if contentType in self._postHandlers:
+ return self._postHandlers[contentType](self, request)
+ else:
+ self.log_info("Get a POST of an unsupported content type on a collection type: %s" % (contentType,))
+ else:
+ self.log_info("Get a POST with no content type on a collection")
+ return responsecode.FORBIDDEN
+
+ _postHandlers = {
+ ("application", "xml") : xmlPOSTAuth,
+ ("text", "xml") : xmlPOSTAuth,
+ }
+
+inviteAccessMapToXML = {
+ "read-only" : customxml.ReadAccess,
+ "read-write" : customxml.ReadWriteAccess,
+ "read-write-schedule" : customxml.ReadWriteScheduleAccess,
+}
+inviteAccessMapFromXML = dict([(v,k) for k,v in inviteAccessMapToXML.iteritems()])
+
+inviteStatusMapToXML = {
+ "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()])
+
+class Invite(object):
+
+ def __init__(self, inviteuid, userid, access, state, summary):
+ self.inviteuid = inviteuid
+ self.userid = userid
+ self.access = access
+ self.state = state
+ self.summary = summary
+
+ def makePropertyElement(self):
+
+ return customxml.InviteUser(
+ customxml.UID.fromString(self.inviteuid),
+ davxml.HRef.fromString(self.userid),
+ customxml.InviteAccess(inviteAccessMapToXML[self.access]()),
+ inviteStatusMapToXML[self.state](),
+ )
+
+class InvitesDatabase(AbstractSQLDatabase, LoggingMixIn):
+
+ db_basename = db_prefix + "invites"
+ schema_version = "1"
+ db_type = "invites"
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+ the shared collection. C{resource} must be a calendar/addressbook collection.)
+ """
+ self.resource = resource
+ db_filename = os.path.join(self.resource.fp.path, InvitesDatabase.db_basename)
+ super(InvitesDatabase, self).__init__(db_filename, True, autocommit=True)
+
+ def create(self):
+ """
+ Create the index and initialize it.
+ """
+ self._db()
+
+ def allRecords(self):
+
+ records = self._db_execute("select * from INVITE order by USERID")
+ return [self._makeRecord(row) for row in (records if records is not None else ())]
+
+ def recordForUserID(self, userid):
+
+ row = self._db_execute("select * from INVITE where USERID = :1", userid)
+ return self._makeRecord(row[0]) if row else None
+
+ def recordForInviteUID(self, inviteUID):
+
+ row = self._db_execute("select * from INVITE where INVITEUID = :1", inviteUID)
+ return self._makeRecord(row[0]) if row else None
+
+ def addOrUpdateRecord(self, record):
+
+ self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, ACCESS, STATE, SUMMARY)
+ values (:1, :2, :3, :4, :5)
+ """, record.inviteuid, record.userid, record.access, record.state, record.summary,
+ )
+
+ def removeRecordForUserID(self, userid):
+
+ self._db_execute("delete from INVITE where USERID = :1", userid)
+
+ def removeRecordForInviteUID(self, inviteUID):
+
+ self._db_execute("delete from INVITE where INVITEUID = :1", inviteUID)
+
+ def remove(self):
+
+ self._db_close()
+ os.remove(self.dbpath)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return InvitesDatabase.schema_version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return InvitesDatabase.db_type
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # INVITE table is the primary table
+ # INVITEUID: UID for this invite
+ # NAME: identifier of invitee
+ # ACCESS: Access mode for share
+ # STATE: Invite response status
+ # SUMMARY: Invite summary
+ #
+ q.execute(
+ """
+ create table INVITE (
+ INVITEUID text unique,
+ USERID text unique,
+ ACCESS text,
+ STATE text,
+ SUMMARY text
+ )
+ """
+ )
+
+ q.execute(
+ """
+ create index USERID on INVITE (USERID)
+ """
+ )
+ q.execute(
+ """
+ create index INVITEUID on INVITE (INVITEUID)
+ """
+ )
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # Nothing to do as we have not changed the schema
+ pass
+
+ def _makeRecord(self, row):
+
+ return Invite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+
+class SharedHomeMixin(object):
+ """
+ A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
+ set of shared calendfars.
+ """
+
+ def sharesDB(self):
+
+ if not hasattr(self, "_sharesDB"):
+ self._sharesDB = SharedCalendarsDatabase(self)
+ return self._sharesDB
+
+ def provisionShares(self):
+
+ if not hasattr(self, "_provisionedShares"):
+ from twistedcaldav.sharedcalendar import SharedCalendarResource
+ for share in self.sharesDB().allRecords():
+ child = SharedCalendarResource(self, share)
+ self.putChild(share.localname, child)
+ self._provisionedShares = True
+
+ @inlineCallbacks
+ def acceptShare(self, request, hostUrl, inviteUID, displayname=None):
+
+ # Do this first to make sure we have a valid share
+ yield self._changeShare(request, "ACCEPTED", hostUrl, inviteUID, displayname)
+
+ # Add or update in DB
+ oldShare = self.sharesDB().recordForInviteUID(inviteUID)
+ if not oldShare:
+ oldShare = share = SharedCalendarRecord(inviteUID, hostUrl, str(uuid4()), displayname)
+ self.sharesDB().addOrUpdateRecord(share)
+
+ # Return the URL of the shared calendar
+ returnValue(XMLResponse(
+ code = responsecode.OK,
+ element = SharedCalendar(
+ davxml.HRef.fromString(joinURL(self.url(), oldShare.localname))
+ )
+ ))
+
+ def wouldAcceptShare(self, hostUrl, request):
+ return succeed(True)
+
+ def removeShare(self, request, share):
+ """ Remove a shared calendar named in resourceName and send a decline """
+ return self.declineShare(request, share.hosturl, share.inviteuid)
+
+ @inlineCallbacks
+ def removeShareByUID(self, request, inviteuid):
+ """ Remove a shared calendar but do not send a decline back """
+
+ record = self.sharesDB().recordForInviteUID(inviteuid)
+ if record:
+ shareURL = joinURL(self.url(), record.localname)
+
+ # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
+ principal = (yield self.resourceOwnerPrincipal(request))
+ inboxURL = principal.scheduleInboxURL()
+ if inboxURL:
+ inbox = (yield request.locateResource(inboxURL))
+ inbox.processFreeBusyCalendar(shareURL, False)
+
+ self.sharesDB().removeRecordForInviteUID(inviteuid)
+
+ returnValue(True)
+
+ @inlineCallbacks
+ def declineShare(self, request, hostUrl, inviteUID):
+
+ # Remove it if its in the DB
+ self.sharesDB().removeRecordForInviteUID(inviteUID)
+
+ yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)
+
+ returnValue(Response(code=responsecode.NO_CONTENT))
+
+ @inlineCallbacks
+ def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None):
+ """ Accept an invite to a shared calendar """
+
+ # Change state in sharer invite
+ owner = (yield self.ownerPrincipal(request))
+ owner = owner.principalURL()
+ sharedCalendar = (yield request.locateResource(hostUrl))
+ if sharedCalendar is None:
+ # Original shared calendar is gone - nothing we can do except ignore it
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "invalid shared calendar",
+ ))
+
+ # Change the record
+ yield sharedCalendar.changeUserInviteState(request, replytoUID, owner, state, displayname)
+
+ yield self.sendReply(request, owner, sharedCalendar, state, hostUrl, replytoUID, displayname)
+
+ @inlineCallbacks
+ def sendReply(self, request, sharee, sharedCalendar, state, hostUrl, replytoUID, displayname=None):
+
+
+ # Locate notifications collection for sharer
+ sharer = (yield sharedCalendar.ownerPrincipal(request))
+ notifications = (yield request.locateResource(sharer.notificationURL()))
+
+ # Generate invite XML
+ notificationUID = "%s-reply" % (replytoUID,)
+ xmltype = customxml.InviteReply()
+ xmldata = customxml.Notification(
+ customxml.DTStamp.fromString(dateTimeToString(datetime.datetime.now(tz=utc))),
+ customxml.InviteReply(
+ *(
+ (
+ davxml.HRef.fromString(sharee),
+ inviteStatusMapToXML[state](),
+ customxml.HostURL(
+ davxml.HRef.fromString(hostUrl),
+ ),
+ customxml.InReplyTo.fromString(replytoUID),
+ ) + ((customxml.InviteSummary.fromString(displayname),) if displayname is not None else ())
+ )
+ ),
+ ).toxml()
+
+ # Add to collections
+ yield notifications.addNotification(request, notificationUID, xmltype, xmldata)
+
+ def xmlPOSTNoAuth(self, encoding, request):
+
+ def _handleErrorResponse(error):
+ if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
+ return error.value.response
+ return Response(code=responsecode.BAD_REQUEST)
+
+ def _handleInviteReply(invitereplydoc):
+ """ Handle a user accepting or declining a sharing invite """
+ hostUrl = None
+ accepted = None
+ summary = None
+ replytoUID = None
+ for item in invitereplydoc.children:
+ if isinstance(item, customxml.InviteStatusAccepted):
+ accepted = True
+ elif isinstance(item, customxml.InviteStatusDeclined):
+ accepted = False
+ elif isinstance(item, customxml.InviteSummary):
+ summary = str(item)
+ elif isinstance(item, customxml.HostURL):
+ for hosturlItem in item.children:
+ if isinstance(hosturlItem, davxml.HRef):
+ hostUrl = str(hosturlItem)
+ elif isinstance(item, customxml.InReplyTo):
+ replytoUID = str(item)
+
+ if accepted is None or hostUrl is None or replytoUID is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (customxml.calendarserver_namespace, "valid-request"),
+ "missing required XML elements",
+ ))
+ if accepted:
+ return self.acceptShare(request, hostUrl, replytoUID, displayname=summary)
+ else:
+ return self.declineShare(request, hostUrl, replytoUID)
+
+ def _getData(data):
+ try:
+ doc = davxml.WebDAVDocument.fromString(data)
+ except ValueError, e:
+ print "Error parsing doc (%s) Doc:\n %s" % (str(e), data,)
+ raise
+
+ root = doc.root_element
+ xmlDocHanders = {
+ customxml.InviteReply: _handleInviteReply,
+ }
+ if type(root) in xmlDocHanders:
+ return xmlDocHanders[type(root)](root).addErrback(_handleErrorResponse)
+ else:
+ self.log_error("Unsupported XML (%s)" % (root,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request")))
+
+ return allDataFromStream(request.stream).addCallback(_getData)
+
+class SharedCalendarRecord(object):
+
+ def __init__(self, inviteuid, hosturl, localname, summary):
+ self.inviteuid = inviteuid
+ self.hosturl = hosturl
+ self.localname = localname
+ self.summary = summary
+
+class SharedCalendarsDatabase(AbstractSQLDatabase, LoggingMixIn):
+
+ db_basename = db_prefix + "shares"
+ schema_version = "1"
+ db_type = "shares"
+
+ def __init__(self, resource):
+ """
+ @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+ the shared collection. C{resource} must be a calendar/addressbook home collection.)
+ """
+ self.resource = resource
+ db_filename = os.path.join(self.resource.fp.path, SharedCalendarsDatabase.db_basename)
+ super(SharedCalendarsDatabase, self).__init__(db_filename, True, autocommit=True)
+
+ def create(self):
+ """
+ Create the index and initialize it.
+ """
+ self._db()
+
+ def allRecords(self):
+
+ records = self._db_execute("select * from SHARES order by LOCALNAME")
+ return [self._makeRecord(row) for row in (records if records is not None else ())]
+
+ def recordForLocalName(self, localname):
+
+ row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
+ return self._makeRecord(row[0]) if row else None
+
+ def recordForInviteUID(self, inviteUID):
+
+ row = self._db_execute("select * from SHARES where INVITEUID = :1", inviteUID)
+ return self._makeRecord(row[0]) if row else None
+
+ def addOrUpdateRecord(self, record):
+
+ self._db_execute("""insert or replace into SHARES (INVITEUID, HOSTURL, LOCALNAME, SUMMARY)
+ values (:1, :2, :3, :4)
+ """, record.inviteuid, record.hosturl, record.localname, record.summary,
+ )
+
+ def removeRecordForLocalName(self, localname):
+
+ self._db_execute("delete from SHARES where LOCALNAME = :1", localname)
+
+ def removeRecordForInviteUID(self, inviteUID):
+
+ self._db_execute("delete from SHARES where INVITEUID = :1", inviteUID)
+
+ def remove(self):
+
+ self._db_close()
+ os.remove(self.dbpath)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return SharedCalendarsDatabase.schema_version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return SharedCalendarsDatabase.db_type
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # SHARES table is the primary table
+ # INVITEUID: UID for this invite
+ # HOSTURL: URL for data source
+ # LOCALNAME: local path name
+ # SUMMARY: Invite summary
+ #
+ q.execute(
+ """
+ create table SHARES (
+ INVITEUID text unique,
+ HOSTURL text,
+ LOCALNAME text,
+ SUMMARY text
+ )
+ """
+ )
+
+ q.execute(
+ """
+ create index INVITEUID on SHARES (INVITEUID)
+ """
+ )
+ q.execute(
+ """
+ create index HOSTURL on SHARES (HOSTURL)
+ """
+ )
+ q.execute(
+ """
+ create index LOCALNAME on SHARES (LOCALNAME)
+ """
+ )
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # Nothing to do as we have not changed the schema
+ pass
+
+ def _makeRecord(self, row):
+
+ return SharedCalendarRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])
Modified: CalendarServer/trunk/twistedcaldav/sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sql.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/sql.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -327,6 +327,7 @@
q = self._db().cursor()
try:
q.execute(sql, query_params)
+ self.lastrowid = q.lastrowid
return q.fetchall()
except DatabaseError:
log.err("Exception while executing SQL on DB %s: %r %r" % (self, sql, query_params))
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/static.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -32,6 +32,8 @@
"DropBoxCollectionFile",
"DropBoxChildFile",
"TimezoneServiceFile",
+ "NotificationCollectionFile",
+ "NotificationFile",
"AddressBookHomeProvisioningFile",
"AddressBookHomeUIDProvisioningFile",
"AddressBookHomeFile",
@@ -45,7 +47,6 @@
from uuid import uuid4
from twext.python.log import Logger
-from twext.web2.dav.http import ErrorResponse
from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue, maybeDeferred
from twisted.python.failure import Failure
@@ -54,12 +55,14 @@
from twext.web2.http import HTTPError, StatusResponse
from twext.web2.dav import davxml
from twext.web2.dav.element.base import dav_namespace
-from twext.web2.dav.fileop import mkcollection, rmdir
+from twext.web2.dav.fileop import mkcollection, rmdir, delete
+from twext.web2.dav.http import ErrorResponse
from twext.web2.dav.idav import IDAVResource
from twext.web2.dav.noneprops import NonePropertyStore
from twext.web2.dav.resource import AccessDeniedError
from twext.web2.dav.resource import davPrivilegeSet
from twext.web2.dav.util import parentForURL, bindMethods, joinURL
+from twext.web2.http_headers import generateContentType, MimeType
from twistedcaldav import caldavxml
from twistedcaldav import carddavxml
@@ -68,6 +71,7 @@
from twistedcaldav.client.reverseproxy import ReverseProxyResource
from twistedcaldav.config import config
from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.extensions import DAVFile, CachingPropertyStore
from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from twistedcaldav.memcacheprops import MemcachePropertyCollection
@@ -78,6 +82,7 @@
from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
from twistedcaldav.resource import isAddressBookCollectionResource, SearchAddressBookResource, SearchAllAddressBookResource
from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, IScheduleInboxResource
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook
@@ -91,11 +96,14 @@
from twistedcaldav.directory.calendar import DirectoryCalendarHomeUIDProvisioningResource
from twistedcaldav.directory.calendar import DirectoryCalendarHomeResource
from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
+from twistedcaldav.sharing import SharedHomeMixin
from twistedcaldav.timezoneservice import TimezoneServiceResource
from twistedcaldav.vcardindex import AddressBookIndex
from twistedcaldav.notify import getPubSubConfiguration, getPubSubXMPPURI
from twistedcaldav.notify import getPubSubHeartbeatURI, getPubSubPath
from twistedcaldav.notify import ClientNotifier, getNodeCacher
+from twistedcaldav.notifications import NotificationCollectionResource,\
+ NotificationResource
log = Logger()
@@ -176,12 +184,6 @@
# CalDAV
##
- def resourceType(self):
- if self.isCalendarCollection():
- return davxml.ResourceType.calendar
- else:
- return super(CalDAVFile, self).resourceType()
-
def createCalendar(self, request):
#
# request object is required because we need to validate against parent
@@ -275,6 +277,7 @@
tzids = set()
isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ accessPrincipal = (yield self.resourceOwnerPrincipal(request))
for name, uid, type in self.index().bruteForceSearch(): #@UnusedVariable
try:
@@ -291,7 +294,7 @@
continue
# Get the access filtered view of the data
- caldata = child.iCalendarTextFiltered(isowner)
+ caldata = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
try:
subcalendar = iComponent.fromString(caldata)
except ValueError:
@@ -313,21 +316,18 @@
raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
- def iCalendarTextFiltered(self, isowner):
+ def iCalendarTextFiltered(self, isowner, accessUID=None):
try:
access = self.readDeadProperty(TwistedCalendarAccessProperty)
except HTTPError:
access = None
- if access in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
+ # Now "filter" the resource calendar data
+ caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
+ if accessUID:
+ caldata = PerUserDataFilter(accessUID).filter(caldata)
+ return str(caldata)
- if not isowner:
- # Now "filter" the resource calendar data through the CALDAV:calendar-data element and apply
- # access restrictions to the data.
- return caldavxml.CalendarData().elementFromResourceWithAccessRestrictions(self, access).calendarData()
-
- return self.iCalendarText()
-
def iCalendarText(self, name=None):
if self.isPseudoCalendarCollection():
if name is None:
@@ -356,9 +356,6 @@
return calendar_data
- def iCalendarXML(self, name=None):
- return caldavxml.CalendarData.fromCalendar(self.iCalendarText(name))
-
def createAddressBook(self, request):
#
# request object is required because we need to validate against parent
@@ -906,7 +903,7 @@
def url(self):
return joinURL(self.parent.url(), self.record.uid)
-class CalendarHomeFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeResource, CalDAVFile):
+class CalendarHomeFile (AutoProvisioningFileMixIn, SharedHomeMixin, DirectoryCalendarHomeResource, CalDAVFile):
"""
Calendar home collection resource.
"""
@@ -924,6 +921,12 @@
CalDAVFile.__init__(self, path)
DirectoryCalendarHomeResource.__init__(self, parent, record)
+ def provision(self):
+ result = super(CalendarHomeFile, self).provision()
+ if config.Sharing.Enabled:
+ self.provisionShares()
+ return result
+
def provisionChild(self, name):
if config.EnableDropBox:
DropBoxHomeFileClass = DropBoxHomeFile
@@ -935,18 +938,23 @@
else:
FreeBusyURLFileClass = None
+ if config.Sharing.Enabled:
+ NotificationCollectionFileClass = NotificationCollectionFile
+ else:
+ NotificationCollectionFileClass = None
+
cls = {
"inbox" : ScheduleInboxFile,
"outbox" : ScheduleOutboxFile,
"dropbox" : DropBoxHomeFileClass,
"freebusy" : FreeBusyURLFileClass,
+ "notification" : NotificationCollectionFileClass,
}.get(name, None)
if cls is not None:
child = cls(self.fp.child(name).path, self)
child.clientNotifier = self.clientNotifier
return child
-
return self.createSimilarFile(self.fp.child(name).path)
def createSimilarFile(self, path):
@@ -1275,6 +1283,60 @@
def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
return succeed(None)
+class NotificationCollectionFile(AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
+ """
+ Notification collection resource.
+ """
+ def __init__(self, path, parent):
+ NotificationCollectionResource.__init__(self)
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+ self.parent = parent
+
+ def createSimilarFile(self, path):
+ if self.comparePath(path):
+ return self
+ else:
+ return NotificationFile(path, self)
+
+ def __repr__(self):
+ return "<%s (notification collection): %s>" % (self.__class__.__name__, self.fp.path)
+
+ def _writeNotification(self, request, uid, rname, xmltype, xmldata):
+
+ # TODO: use the generic StoreObject api so that quota, sync-token etc all get changed properly
+ child = self.createSimilarFile(self.fp.child(rname).path)
+ child.fp.setContent(xmldata)
+ child.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"}))))
+ child.writeDeadProperty(customxml.NotificationType(xmltype))
+
+ return succeed(True)
+
+ def _deleteNotification(self, request, rname):
+
+ # TODO: use the generic DeleteResource api so that quota, sync-token etc all get changed properly
+ childfp = self.fp.child(rname)
+ return delete("", childfp)
+
+class NotificationFile(NotificationResource, CalDAVFile):
+
+ def __init__(self, path, parent):
+ NotificationResource.__init__(self, parent)
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+
+ assert self.fp.isfile() or not self.fp.exists()
+
+ def createSimilarFile(self, path):
+ if self.comparePath(path):
+ return self
+ else:
+ return responsecode.NOT_FOUND
+
+ def __repr__(self):
+ return "<%s (notification file): %s>" % (self.__class__.__name__, self.fp.path)
+
+ def resourceName(self):
+ return self.fp.basename()
+
class AddressBookHomeProvisioningFile (DirectoryAddressBookHomeProvisioningResource, DAVFile):
"""
Resource which provisions address book home collections as needed.
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -339,7 +339,20 @@
"EnableDropBox" : False, # Calendar Drop Box
"EnablePrivateEvents" : False, # Private Events
"EnableTimezoneService" : False, # Timezone service
+
+ "Sharing": {
+ "Enabled" : False, # Overall on/off switch
+ "AllowExternalUsers" : False, # External (non-principal) sharees allowed
+ "Calendars" : {
+ "Enabled" : False, # Calendar on/off switch
+ "AllowScheduling" : False, # Scheduling in shared calendars
+ },
+ "AddressBooks" : {
+ "Enabled" : False, # Address Books on/off switch
+ }
+ },
+
#
# Web-based administration
#
@@ -901,6 +914,14 @@
log.info("iMIP %s password not found in keychain" %
(direction,))
+def _updateSharing(configDict):
+ #
+ # FIXME: Use the config object instead of doing this here
+ #
+ from twistedcaldav.resource import CalDAVResource, CalendarPrincipalResource
+ CalDAVResource.enableSharing(configDict.Sharing.Enabled)
+ CalendarPrincipalResource.enableSharing(configDict.Sharing.Enabled)
+
def _updatePartitions(configDict):
if configDict.Partitioning.Enabled:
partitions.setSelfPartition(configDict.Partitioning.ServerPartitionID)
@@ -925,8 +946,11 @@
if configDict.EnableCardDAV:
compliance += carddavxml.carddav_compliance
- compliance += customxml.calendarserver_principal_property_search
+ compliance += customxml.calendarserver_principal_property_search_compliance
+ if config.Sharing.Enabled:
+ compliance += customxml.calendarserver_sharing_compliance
+
configDict.CalDAVComplianceClasses = compliance
@@ -946,6 +970,7 @@
_updateLogLevels,
_updateNotifications,
_updateScheduling,
+ _updateSharing,
_updatePartitions,
_updateCompliance,
)
Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -30,6 +30,7 @@
from twistedcaldav import caldavxml
from twistedcaldav import ical
from twistedcaldav.index import db_basename
+from twistedcaldav.query import queryfilter
class CalendarQuery (twistedcaldav.test.util.TestCase):
"""
@@ -116,7 +117,7 @@
cal = property.calendar()
instances = cal.expandTimeRanges(query_timerange.end)
vevents = [x for x in cal.subcomponents() if x.name() == "VEVENT"]
- if not query_timerange.matchinstance(vevents[0], instances):
+ if not queryfilter.TimeRange(query_timerange).matchinstance(vevents[0], instances):
self.fail("REPORT property %r returned calendar %s outside of request time range %r"
% (property, property.calendar, query_timerange))
Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -3124,6 +3124,245 @@
elif changed:
self.fail("Truncation happened when not expected: %s" % (title,))
+ def test_valid_recurrence(self):
+
+ data = (
+ (
+ "1.1 - no recurrence",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.2 - rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RDATE:20091004T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 5, 0, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.3 - rrule no overrides",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.4 - rrule no overrides + rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+RDATE:20091004T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 2, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.5 - rrule no overrides + rdate + exdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+RDATE:20091004T010000Z
+EXDATE:20091003T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 2, 0, 0, tzinfo=tzutc()), False),
+ (datetime.datetime(2009, 10, 3, 0, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.6 - rrule with override",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 1, 0, 0, tzinfo=tzutc()), False),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.7 - rrule + rdate with override",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+RDATE:20071115T010000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T010000Z
+DTSTART:20071115T020000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 1, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2007, 11, 15, 2, 0, 0, tzinfo=tzutc()), False),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 1, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.8 - override only",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, False),
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), False),
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), True),
+ (datetime.datetime(2009, 10, 4, 0, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ (
+ "1.9 - no recurrence one test master",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (None, True),
+ )
+ ),
+ (
+ "1.10 - no recurrence one test master",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (datetime.datetime(2007, 11, 14, 0, 0, 0, tzinfo=tzutc()), True),
+ )
+ ),
+ (
+ "1.11 - no recurrence one test missing",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ (datetime.datetime(2007, 11, 15, 0, 0, 0, tzinfo=tzutc()), False),
+ )
+ ),
+ )
+
+ for clear_cache in (True, False):
+ for title, calendar, tests in data:
+ ical = Component.fromString(calendar)
+ for ctr, item in enumerate(tests):
+ rid, result = item
+ self.assertEqual(ical.validInstance(rid, clear_cache=clear_cache), result, "Failed comparison: %s #%d" % (title, ctr+1,))
+
+ for title, calendar, tests in data:
+ ical = Component.fromString(calendar)
+ rids = set([rid for rid, result in tests])
+ expected_results = set([rid for rid, result in tests if result==True])
+ actual_results = ical.validInstances(rids)
+ self.assertEqual(actual_results, expected_results, "Failed comparison: %s %s" % (title, actual_results,))
+
def test_mismatched_until(self):
invalid = (
"""BEGIN:VCALENDAR
@@ -3220,3 +3459,519 @@
calendar.validateForCalDAV()
except:
self.fail("Valid calendar should validate")
+
+ def test_allperuseruids(self):
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ calendar = Component.fromString(data)
+ self.assertEqual(calendar.allPerUserUIDs(), set((
+ "user01",
+ "user02",
+ )))
+
+ def test_perUserTransparency(self):
+ data = (
+ (
+ "No per-user, not recurring 1.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", True,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Single user, not recurring 1.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Two users, not recurring 1.3",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ("user02", True,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "No per-user, simple recurring 2.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Single user, simple recurring 2.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", False,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Two users, simple recurring 2.3",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ("user02", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", False,),
+ ("user02", True,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "No per-user, complex recurring 3.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+TRANSP:TRANSPARENT
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", True,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Single user, complex recurring 3.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", False,),
+ ),
+ ),
+ ),
+ ),
+ (
+ "Two users, complex recurring 3.3",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080604T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ (
+ (
+ None,
+ (
+ ("", False,),
+ ("user01", False,),
+ ("user02", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", True,),
+ ("user02", False,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", True,),
+ ("user02", True,),
+ ),
+ ),
+ (
+ datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+ (
+ ("", False,),
+ ("user01", False,),
+ ("user02", True,),
+ ),
+ ),
+ ),
+ ),
+ )
+
+ for title, text, results in data:
+ calendar = Component.fromString(text)
+ for rid, result in results:
+ self.assertEqual(calendar.perUserTransparency(rid), result, "Failed comparison: %s %s" % (title, rid,))
Modified: CalendarServer/trunk/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_index.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_index.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -17,16 +17,17 @@
from twisted.internet import reactor
from twisted.internet.task import deferLater
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
from twistedcaldav.ical import Component
from twistedcaldav.index import Index, default_future_expansion_duration,\
maximum_future_expansion_duration, IndexedSearchException,\
AbstractCalendarIndex, icalfbtype_to_indexfbtype
from twistedcaldav.index import ReservationError, MemcachedUIDReserver
from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.query import queryfilter
from twistedcaldav.test.util import InMemoryMemcacheProtocol
import twistedcaldav.test.util
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import TimeRange
from vobject.icalendar import utc
import sqlite3
@@ -278,7 +279,7 @@
""",
"20080601T000000Z", "20080602T000000Z",
"mailto:user1 at example.com",
- (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B'),),
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
),
(
"#1.2 Simple component - transparent",
@@ -299,7 +300,7 @@
""",
"20080602T000000Z", "20080603T000000Z",
"mailto:user1 at example.com",
- (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'F'),),
+ (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
),
(
"#1.3 Simple component - canceled",
@@ -320,7 +321,7 @@
""",
"20080603T000000Z", "20080604T000000Z",
"mailto:user1 at example.com",
- (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F'),),
+ (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
),
(
"#1.4 Simple component - tentative",
@@ -341,7 +342,7 @@
""",
"20080604T000000Z", "20080605T000000Z",
"mailto:user1 at example.com",
- (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T'),),
+ (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
),
(
"#2.1 Recurring component - busy",
@@ -363,8 +364,8 @@
"20080605T000000Z", "20080607T000000Z",
"mailto:user1 at example.com",
(
- ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B'),
- ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B'),
+ ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
),
),
(
@@ -397,8 +398,8 @@
"20080607T000000Z", "20080609T000000Z",
"mailto:user1 at example.com",
(
- ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B'),
- ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'F'),
+ ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
),
),
)
@@ -428,15 +429,419 @@
name="VCALENDAR",
)
)
+ filter = queryfilter.Filter(filter)
resources = self.db.indexedSearch(filter, fbtype=True)
index_results = set()
- for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype in resources:
+ for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
self.assertEqual(test_organizer, organizer, msg=description)
- index_results.add((float, start, end, fbtype,))
+ index_results.add((float, start, end, fbtype, transp,))
self.assertEqual(set(instances), index_results, msg=description)
+ def test_index_timespan_per_user(self):
+ data = (
+ (
+ "#1.1 Single per-user non-recurring component",
+ "1.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080602T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+ ),
+ (
+ "user02",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ ),
+ ),
+ (
+ "#1.2 Two per-user non-recurring component",
+ "1.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080602T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+ ),
+ (
+ "user02",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ (
+ "user03",
+ (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+ ),
+ ),
+ ),
+ (
+ "#2.1 Single per-user simple recurring component",
+ "2.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080603T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#2.2 Two per-user simple recurring component",
+ "2.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080603T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ (
+ "user03",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#3.1 Single per-user complex recurring component",
+ "3.1",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.1
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080604T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ (
+ "#3.2 Two per-user complex recurring component",
+ "3.2",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.2
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+ "20080601T000000Z", "20080604T000000Z",
+ "mailto:user1 at example.com",
+ (
+ (
+ "user01",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user02",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+ ),
+ ),
+ (
+ "user03",
+ (
+ ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+ ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+ ),
+ ),
+ ),
+ ),
+ )
+
+ revision = 0
+ for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
+ revision += 1
+ calendar = Component.fromString(calendar_txt)
+
+ f = open(os.path.join(self.site.resource.fp.path, name), "w")
+ f.write(calendar_txt)
+ del f
+
+ self.db.addResource(name, calendar, revision)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ # Create fake filter element to match time-range
+ filter = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ TimeRange(
+ start=trstart,
+ end=trend,
+ ),
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
+ filter = queryfilter.Filter(filter)
+
+ for useruid, instances in peruserinstances:
+ resources = self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
+ index_results = set()
+ for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+ self.assertEqual(test_organizer, organizer, msg=description)
+ index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
+
+ self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
+
+ revision += 1
+ self.db.deleteResource(name, revision)
+
def test_index_revisions(self):
data1 = """BEGIN:VCALENDAR
VERSION:2.0
@@ -500,424 +905,6 @@
for revision, results in tests:
self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
-class SQLIndexUpgradeTests (twistedcaldav.test.util.TestCase):
- """
- Test abstract SQL DB class
- """
-
- class OldIndexv6(Index):
-
- def _db_version(self):
- """
- @return: the schema version assigned to this index.
- """
- return "6"
-
- def _db_init_data_tables_base(self, q, uidunique):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
- #
- # RESOURCE table is the primary index table
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # UID: iCalendar UID (may or may not be unique)
- # TYPE: iCalendar component type
- # RECURRANCE_MAX: Highest date of recurrence expansion
- #
- if uidunique:
- q.execute(
- """
- create table RESOURCE (
- NAME text unique,
- UID text unique,
- TYPE text,
- RECURRANCE_MAX date
- )
- """
- )
- else:
- q.execute(
- """
- create table RESOURCE (
- NAME text unique,
- UID text,
- TYPE text,
- RECURRANCE_MAX date
- )
- """
- )
-
- #
- # TIMESPAN table tracks (expanded) timespans for resources
- # NAME: Related resource (RESOURCE foreign key)
- # FLOAT: 'Y' if start/end are floating, 'N' otherwise
- # START: Start date
- # END: End date
- #
- q.execute(
- """
- create table TIMESPAN (
- NAME text,
- FLOAT text(1),
- START date,
- END date
- )
- """
- )
-
- if uidunique:
- #
- # RESERVED table tracks reserved UIDs
- # UID: The UID being reserved
- # TIME: When the reservation was made
- #
- q.execute(
- """
- create table RESERVED (
- UID text unique,
- TIME date
- )
- """
- )
-
- def _db_upgrade(self, old_version):
- """
- Upgrade the database tables.
- """
-
- return super(AbstractCalendarIndex, self)._db_upgrade(old_version)
-
- def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
- """
- Records the given calendar resource in the index with the given name.
- Resource names and UIDs must both be unique; only one resource name may
- be associated with any given UID and vice versa.
- NB This method does not commit the changes to the db - the caller
- MUST take care of that
- @param name: the name of the resource to add.
- @param calendar: a L{Calendar} object representing the resource
- contents.
- """
- uid = calendar.resourceUID()
-
- # Decide how far to expand based on the component
- master = calendar.masterComponent()
- if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
- # When there is no master we have a set of overridden components - index them all.
- # When there is one instance - index it.
- # When bounded - index all.
- expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- else:
- if expand_until:
- expand = expand_until
- else:
- expand = datetime.date.today() + default_future_expansion_duration
-
- if expand > (datetime.date.today() + maximum_future_expansion_duration):
- raise IndexedSearchException
-
- try:
- instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
- except InvalidOverriddenInstanceError:
- raise
-
- self._delete_from_db(name, uid, None)
-
- for key in instances:
- instance = instances[key]
- start = instance.start.replace(tzinfo=utc)
- end = instance.end.replace(tzinfo=utc)
- float = 'Y' if instance.start.tzinfo is None else 'N'
- self._db_execute(
- """
- insert into TIMESPAN (NAME, FLOAT, START, END)
- values (:1, :2, :3, :4)
- """, name, float, start, end
- )
-
- # Special - for unbounded recurrence we insert a value for "infinity"
- # that will allow an open-ended time-range to always match it.
- if calendar.isRecurringUnbounded():
- start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
- float = 'N'
- self._db_execute(
- """
- insert into TIMESPAN (NAME, FLOAT, START, END)
- values (:1, :2, :3, :4)
- """, name, float, start, end
- )
-
- self._db_execute(
- """
- insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX)
- values (:1, :2, :3, :4)
- """, name, uid, calendar.resourceType(), instances.limit
- )
-
- class OldIndexv7(Index):
-
- def _db_version(self):
- """
- @return: the schema version assigned to this index.
- """
- return "7"
-
- def _db_init_data_tables_base(self, q, uidunique):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
- #
- # RESOURCE table is the primary index table
- # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
- # UID: iCalendar UID (may or may not be unique)
- # TYPE: iCalendar component type
- # RECURRANCE_MAX: Highest date of recurrence expansion
- # ORGANIZER: cu-address of the Organizer of the event
- #
- if uidunique:
- q.execute(
- """
- create table RESOURCE (
- NAME text unique,
- UID text unique,
- TYPE text,
- RECURRANCE_MAX date,
- ORGANIZER text
- )
- """
- )
- else:
- q.execute(
- """
- create table RESOURCE (
- NAME text unique,
- UID text,
- TYPE text,
- RECURRANCE_MAX date
- )
- """
- )
-
- #
- # TIMESPAN table tracks (expanded) time spans for resources
- # NAME: Related resource (RESOURCE foreign key)
- # FLOAT: 'Y' if start/end are floating, 'N' otherwise
- # START: Start date
- # END: End date
- # FBTYPE: FBTYPE value:
- # '?' - unknown
- # 'F' - free
- # 'B' - busy
- # 'U' - busy-unavailable
- # 'T' - busy-tentative
- #
- q.execute(
- """
- create table TIMESPAN (
- NAME text,
- FLOAT text(1),
- START date,
- END date,
- FBTYPE text(1)
- )
- """
- )
-
- if uidunique:
- #
- # RESERVED table tracks reserved UIDs
- # UID: The UID being reserved
- # TIME: When the reservation was made
- #
- q.execute(
- """
- create table RESERVED (
- UID text unique,
- TIME date
- )
- """
- )
-
- def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
- """
- Records the given calendar resource in the index with the given name.
- Resource names and UIDs must both be unique; only one resource name may
- be associated with any given UID and vice versa.
- NB This method does not commit the changes to the db - the caller
- MUST take care of that
- @param name: the name of the resource to add.
- @param calendar: a L{Calendar} object representing the resource
- contents.
- """
- uid = calendar.resourceUID()
- organizer = calendar.getOrganizer()
- if not organizer:
- organizer = ""
-
- # Decide how far to expand based on the component
- master = calendar.masterComponent()
- if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
- # When there is no master we have a set of overridden components - index them all.
- # When there is one instance - index it.
- # When bounded - index all.
- expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- else:
- if expand_until:
- expand = expand_until
- else:
- expand = datetime.date.today() + default_future_expansion_duration
-
- if expand > (datetime.date.today() + maximum_future_expansion_duration):
- raise IndexedSearchException
-
- try:
- instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
- except InvalidOverriddenInstanceError:
- raise
-
- self._delete_from_db(name, uid, None)
-
- for key in instances:
- instance = instances[key]
- start = instance.start.replace(tzinfo=utc)
- end = instance.end.replace(tzinfo=utc)
- float = 'Y' if instance.start.tzinfo is None else 'N'
- self._db_execute(
- """
- insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
- values (:1, :2, :3, :4, :5)
- """, name, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
- )
-
- # Special - for unbounded recurrence we insert a value for "infinity"
- # that will allow an open-ended time-range to always match it.
- if calendar.isRecurringUnbounded():
- start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
- end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
- float = 'N'
- self._db_execute(
- """
- insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
- values (:1, :2, :3, :4, :5)
- """, name, float, start, end, '?'
- )
-
- self._db_execute(
- """
- insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
- values (:1, :2, :3, :4, :5)
- """, name, uid, calendar.resourceType(), instances.limit, organizer
- )
-
- def setUp(self):
- super(SQLIndexUpgradeTests, self).setUp()
- self.site.resource.isCalendarCollection = lambda: True
- self.db = Index(self.site.resource)
- self.olddbv6 = SQLIndexUpgradeTests.OldIndexv6(self.site.resource)
- self.olddbv7 = SQLIndexUpgradeTests.OldIndexv7(self.site.resource)
-
- def prepareOldDB(self):
- if os.path.exists(self.olddbv6.dbpath):
- os.remove(self.olddbv6.dbpath)
-
- def test_old_schema(self):
-
- for olddb in (self.olddbv6, self.olddbv7):
- self.prepareOldDB()
-
- schema = olddb._db_value_for_sql(
- """
- select VALUE from CALDAV
- where KEY = 'SCHEMA_VERSION'
- """)
- self.assertEqual(schema, olddb._db_version())
-
- def test_empty_upgrade(self):
-
- for olddb in (self.olddbv6, self.olddbv7):
- self.prepareOldDB()
-
- schema = olddb._db_value_for_sql(
- """
- select VALUE from CALDAV
- where KEY = 'SCHEMA_VERSION'
- """)
- self.assertEqual(schema, olddb._db_version())
-
- if olddb._db_version() == "6":
- self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select ORGANIZER from RESOURCE")
- self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select FBTYPE from TIMESPAN")
- elif olddb._db_version() == "7":
- olddb._db_value_for_sql("select ORGANIZER from RESOURCE")
- olddb._db_value_for_sql("select FBTYPE from TIMESPAN")
- self.assertEqual(set([row[1] for row in olddb._db_execute("PRAGMA index_list(TIMESPAN)")]), set())
-
- schema = self.db._db_value_for_sql(
- """
- select VALUE from CALDAV
- where KEY = 'SCHEMA_VERSION'
- """)
- self.assertEqual(schema, self.db._db_version())
-
- value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE")
- self.assertEqual(value, None)
- self.assertEqual(set([row[1] for row in self.db._db_execute("PRAGMA index_list(TIMESPAN)")]), set(("STARTENDFLOAT",)))
-
- def test_basic_upgrade(self):
-
- for olddb in (self.olddbv6, self.olddbv7):
- self.prepareOldDB()
-
- calendar_name = "1.ics"
- calendar_data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
-
- olddb.addResource(calendar_name, Component.fromString(calendar_data), 1)
- self.assertTrue(olddb.resourceExists(calendar_name))
-
- if olddb._db_version() == "6":
- self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select ORGANIZER from RESOURCE")
- self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select FBTYPE from TIMESPAN")
- elif olddb._db_version() == "7":
- olddb._db_value_for_sql("select ORGANIZER from RESOURCE")
- olddb._db_value_for_sql("select FBTYPE from TIMESPAN")
- self.assertEqual(set([row[1] for row in olddb._db_execute("PRAGMA index_list(TIMESPAN)")]), set())
-
- value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE where NAME = :1", calendar_name)
- if olddb._db_version() == "6":
- self.assertEqual(value, "?")
- else:
- self.assertEqual(value, "mailto:user1 at example.com")
-
- value = self.db._db_value_for_sql("select FBTYPE from TIMESPAN where NAME = :1", calendar_name)
- if olddb._db_version() == "6":
- self.assertEqual(value, "?")
- else:
- self.assertEqual(value, "B")
-
- self.db.addResource(calendar_name, Component.fromString(calendar_data), 2)
- self.assertTrue(olddb.resourceExists(calendar_name))
-
- value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE where NAME = :1", calendar_name)
- self.assertEqual(value, "mailto:user1 at example.com")
-
- value = self.db._db_value_for_sql("select FBTYPE from TIMESPAN where NAME = :1", calendar_name)
- self.assertEqual(value, "B")
-
class MemcacheTests(SQLIndexTests):
def setUp(self):
super(MemcacheTests, self).setUp()
Modified: CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -189,3 +189,94 @@
"val0")
self.assertEquals(child2.deadProperties().get(("ns1:", "prop3")).value,
"val0")
+
+ def test_setget_uids(self):
+
+ for uid in (None, "123", "456"):
+ child1 = self.getColl().getChild("a")
+ child1.deadProperties().set(StubProperty("ns1:", "prop1", value="val1%s" % (uid if uid else "",)), uid=uid)
+
+ child2 = self.getColl().getChild("a")
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val1%s" % (uid if uid else "",))
+
+ child2.deadProperties().set(StubProperty("ns1:", "prop1", value="val2%s" % (uid if uid else "",)), uid=uid)
+
+ # force memcache to be consulted (once per collection per request)
+ child1 = self.getColl().getChild("a")
+
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val2%s" % (uid if uid else "",))
+
+ def test_merge_uids(self):
+
+ for uid in (None, "123", "456"):
+ child1 = self.getColl().getChild("a")
+ child2 = self.getColl().getChild("a")
+ child1.deadProperties().set(StubProperty("ns1:", "prop1", value="val0%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().set(StubProperty("ns1:", "prop2", value="val0%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().set(StubProperty("ns1:", "prop3", value="val0%s" % (uid if uid else "",)), uid=uid)
+
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+
+ child2.deadProperties().set(StubProperty("ns1:", "prop1", value="val1%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().set(StubProperty("ns1:", "prop3", value="val3%s" % (uid if uid else "",)), uid=uid)
+
+ # force memcache to be consulted (once per collection per request)
+ child2 = self.getColl().getChild("a")
+
+ # verify properties
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val1%s" % (uid if uid else "",))
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val3%s" % (uid if uid else "",))
+
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val1%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val3%s" % (uid if uid else "",))
+
+ def test_delete_uids(self):
+
+ for uid in (None, "123", "456"):
+ child1 = self.getColl().getChild("a")
+ child2 = self.getColl().getChild("a")
+ child1.deadProperties().set(StubProperty("ns1:", "prop1", value="val0%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().set(StubProperty("ns1:", "prop2", value="val0%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().set(StubProperty("ns1:", "prop3", value="val0%s" % (uid if uid else "",)), uid=uid)
+
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop1"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+
+ child2.deadProperties().set(StubProperty("ns1:", "prop1", value="val1%s" % (uid if uid else "",)), uid=uid)
+ child1.deadProperties().delete(("ns1:", "prop1"), uid=uid)
+ self.assertRaises(HTTPError, child1.deadProperties().get, ("ns1:", "prop1"), uid=uid)
+
+ self.assertFalse(child1.deadProperties().contains(("ns1:", "prop1"), uid=uid))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child1.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+
+ # force memcache to be consulted (once per collection per request)
+ child2 = self.getColl().getChild("a")
+
+ # verify properties
+ self.assertFalse(child2.deadProperties().contains(("ns1:", "prop1"), uid=uid))
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop2"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
+ self.assertEquals(child2.deadProperties().get(("ns1:", "prop3"), uid=uid).value,
+ "val0%s" % (uid if uid else "",))
Modified: CalendarServer/trunk/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_resource.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_resource.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -22,7 +22,7 @@
class StubProperty(object):
def qname(self):
- return "StubQname"
+ return "StubQnamespace", "StubQname"
class CalDAVResourceTests(TestCase):
@@ -34,5 +34,5 @@
def test_writeDeadPropertyWritesProperty(self):
prop = StubProperty()
self.resource.writeDeadProperty(prop)
- self.assertEquals(self.resource._dead_properties.get("StubQname"),
+ self.assertEquals(self.resource._dead_properties.get(("StubQnamespace", "StubQname")),
prop)
Copied: CalendarServer/trunk/twistedcaldav/test/test_sharing.py (from rev 5440, CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_sharing.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -0,0 +1,502 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
+from twext.web2.test.test_server import SimpleRequest
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav import customxml
+from twistedcaldav.config import config
+from twistedcaldav.static import CalDAVFile
+from twistedcaldav.test.util import InMemoryPropertyStore
+from twistedcaldav.test.util import TestCase
+import os
+from twext.web2.http import HTTPError
+
+
+class SharingTests(TestCase):
+ def setUp(self):
+ super(SharingTests, self).setUp()
+ config.Sharing.Enabled = True
+ config.Sharing.Calendars.Enabled = True
+
+ collection = self.mktemp()
+ os.mkdir(collection)
+ self.resource = CalDAVFile(collection, self.site.resource)
+ self.resource._dead_properties = InMemoryPropertyStore()
+ self.resource.writeDeadProperty(davxml.ResourceType.calendar)
+ self.site.resource.putChild("calendar", self.resource)
+
+ self.resource.validUserIDForShare = self._fakeValidUserID
+ self.resource.sendInvite = lambda record, request:succeed(True)
+ self.resource.removeInvite = lambda record, request:succeed(True)
+
+ class FakePrincipal(object):
+
+ def __init__(self, cuaddr):
+ self.path = "/principals/__uids__/%s" % (cuaddr[7:].split('@')[0],)
+ self.homepath = "/calendars/__uids__/%s" % (cuaddr[7:].split('@')[0],)
+
+ def calendarHome(self):
+ class FakeHome(object):
+ def removeShareByUID(self, request, uid):pass
+ return FakeHome()
+
+ self.resource.principalForCalendarUserAddress = lambda cuaddr: FakePrincipal(cuaddr)
+
+ def _fakeValidUserID(self, userid):
+ if userid.endswith("@example.com"):
+ return userid
+ else:
+ return None
+
+ def _fakeInvalidUserID(self, userid):
+ if userid.endswith("@example.net"):
+ return userid
+ else:
+ return None
+
+ @inlineCallbacks
+ def _doPOST(self, body, resultcode = responsecode.OK):
+ request = SimpleRequest(self.site, "POST", "/calendar/")
+ request.headers.setHeader("content-type", MimeType("text", "xml"))
+ request.stream = MemoryStream(body)
+
+ response = (yield self.send(request, None))
+ self.assertEqual(response.code, resultcode)
+ returnValue(response)
+
+ def _clearUIDElementValue(self, xml):
+
+ for user in xml.children:
+ for element in user.children:
+ if type(element) == customxml.UID:
+ element.children[0].data = ""
+ return xml
+
+ @inlineCallbacks
+ def test_upgradeToShareOnCreate(self):
+ request = SimpleRequest(self.site, "MKCOL", "/calendar/")
+
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.calendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, request))
+ self.assertEquals(propInvite, None)
+
+ yield self.resource.upgradeToShare(request)
+
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, request))
+ self.assertEquals(propInvite, customxml.Invite())
+
+ isShared = (yield self.resource.isShared(request))
+ self.assertTrue(isShared)
+ isVShared = (yield self.resource.isVirtualShare(request))
+ self.assertFalse(isVShared)
+
+ @inlineCallbacks
+ def test_upgradeToShareAfterCreate(self):
+ request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
+
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.calendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, request))
+ self.assertEquals(propInvite, None)
+
+ yield self.resource.upgradeToShare(request)
+
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, request))
+ self.assertEquals(propInvite, customxml.Invite())
+
+ isShared = (yield self.resource.isShared(request))
+ self.assertTrue(isShared)
+ isVShared = (yield self.resource.isVirtualShare(request))
+ self.assertFalse(isVShared)
+
+ @inlineCallbacks
+ def test_downgradeFromShare(self):
+ request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
+
+ self.resource.writeDeadProperty(davxml.ResourceType.sharedcalendar)
+ self.resource.writeDeadProperty(customxml.Invite())
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(propInvite, customxml.Invite())
+
+ yield self.resource.downgradeFromShare(None)
+
+ rtype = (yield self.resource.resourceType(request))
+ self.assertEquals(rtype, davxml.ResourceType.calendar)
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(propInvite, None)
+
+ isShared = (yield self.resource.isShared(None))
+ self.assertFalse(isShared)
+ isVShared = (yield self.resource.isVirtualShare(None))
+ self.assertFalse(isVShared)
+
+ @inlineCallbacks
+ def test_POSTaddInviteeAlreadyShared(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ )
+ ))
+
+ isShared = (yield self.resource.isShared(None))
+ self.assertTrue(isShared)
+ isVShared = (yield self.resource.isVirtualShare(None))
+ self.assertFalse(isVShared)
+
+ @inlineCallbacks
+ def test_POSTaddInviteeNotAlreadyShared(self):
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+"""
+ )
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ )
+ ))
+
+ isShared = (yield self.resource.isShared(None))
+ self.assertTrue(isShared)
+ isVShared = (yield self.resource.isVirtualShare(None))
+ self.assertFalse(isVShared)
+
+ @inlineCallbacks
+ def test_POSTupdateInvitee(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read/>
+ </CS:set>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadAccess()),
+ customxml.InviteStatusNoResponse(),
+ )
+ ))
+
+ @inlineCallbacks
+ def test_POSTremoveInvitee(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:remove>
+ <D:href>mailto:user02 at example.com</D:href>
+ </CS:remove>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+
+ @inlineCallbacks
+ def test_POSTaddMoreInvitees(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user03 at example.com</D:href>
+ <CS:summary>Your Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+ <CS:set>
+ <D:href>mailto:user04 at example.com</D:href>
+ <CS:summary>Your Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user03 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user04 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ ))
+
+ @inlineCallbacks
+ def test_POSTaddRemoveInvitees(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+ <CS:set>
+ <D:href>mailto:user03 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:remove>
+ <D:href>mailto:user03 at example.com</D:href>
+ </CS:remove>
+ <CS:set>
+ <D:href>mailto:user04 at example.com</D:href>
+ <CS:summary>Your Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user04 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ ))
+
+ @inlineCallbacks
+ def test_POSTaddRemoveSameInvitee(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user02 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+ <CS:set>
+ <D:href>mailto:user03 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:remove>
+ <D:href>mailto:user03 at example.com</D:href>
+ </CS:remove>
+ <CS:set>
+ <D:href>mailto:user03 at example.com</D:href>
+ <CS:summary>Your Shared Calendar</CS:summary>
+ <CS:read/>
+ </CS:set>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user02 at example.com"),
+ customxml.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ customxml.InviteUser(
+ customxml.UID.fromString(""),
+ davxml.HRef.fromString("mailto:user03 at example.com"),
+ customxml.InviteAccess(customxml.ReadAccess()),
+ customxml.InviteStatusNoResponse(),
+ ),
+ ))
+
+ @inlineCallbacks
+ def test_POSTaddInvalidInvitee(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ response = (yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:bogus at example.net</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""",
+ responsecode.MULTI_STATUS
+ ))
+
+ self.assertEqual(
+ str(response.stream.read()).replace("\r\n", "\n"),
+ """<?xml version='1.0' encoding='UTF-8'?>
+<multistatus xmlns='DAV:'>
+ <response>
+ <href>mailto:bogus at example.net</href>
+ <status>HTTP/1.1 403 Forbidden</status>
+ </response>
+</multistatus>"""
+ )
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+
+ @inlineCallbacks
+ def test_POSTremoveInvalidInvitee(self):
+
+ yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:set>
+ <D:href>mailto:user01 at example.com</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
+""")
+
+ 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.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusNoResponse(),
+ )
+ ))
+
+ self.resource.validUserIDForShare = self._fakeInvalidUserID
+
+ 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.InviteAccess(customxml.ReadWriteAccess()),
+ customxml.InviteStatusInvalid(),
+ )
+ ))
+
+ yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+ <CS:remove>
+ <D:href>mailto:user01 at example.com</D:href>
+ </CS:remove>
+</CS:share>
+""")
+
+ propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+ self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
Modified: CalendarServer/trunk/twistedcaldav/test/test_xml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_xml.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/test_xml.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -18,8 +18,10 @@
from twisted.trial.unittest import SkipTest
from twistedcaldav.ical import Component
-from twistedcaldav.caldavxml import *
+from twistedcaldav.query import queryfilter
import twistedcaldav.test.util
+from twistedcaldav.caldavxml import ComponentFilter, PropertyFilter, TextMatch,\
+ Filter, TimeRange
class XML (twistedcaldav.test.util.TestCase):
"""
@@ -41,11 +43,13 @@
if has: no = "no "
else: no = ""
- if has != ComponentFilter(
+ if has != queryfilter.ComponentFilter(
ComponentFilter(
- name=component_name
- ),
- name="VCALENDAR"
+ ComponentFilter(
+ name=component_name
+ ),
+ name="VCALENDAR"
+ )
).match(self.calendar, None):
self.fail("Calendar has %s%s?" % (no, component_name))
@@ -60,14 +64,16 @@
if has: no = "no "
else: no = ""
- if has != ComponentFilter(
+ if has != queryfilter.ComponentFilter(
ComponentFilter(
- PropertyFilter(
- name=property_name
+ ComponentFilter(
+ PropertyFilter(
+ name=property_name
+ ),
+ name="VEVENT"
),
- name="VEVENT"
- ),
- name="VCALENDAR"
+ name="VCALENDAR"
+ )
).match(self.calendar, None):
self.fail("Calendar has %sVEVENT with %s?" % (no, property_name))
@@ -90,15 +96,17 @@
if has: no = "no "
else: no = ""
- if has != ComponentFilter(
+ if has != queryfilter.ComponentFilter(
ComponentFilter(
- PropertyFilter(
- TextMatch.fromString(uid, caseless=caseless),
- name="UID"
+ ComponentFilter(
+ PropertyFilter(
+ TextMatch.fromString(uid, caseless=caseless),
+ name="UID"
+ ),
+ name="VEVENT"
),
- name="VEVENT"
- ),
- name="VCALENDAR"
+ name="VCALENDAR"
+ )
).match(self.calendar, None):
self.fail("Calendar has %sVEVENT with UID %s? (caseless=%s)" % (no, uid, caseless))
@@ -127,13 +135,17 @@
if has: no = "no "
else: no = ""
- if has != Filter(ComponentFilter(
- ComponentFilter(
- TimeRange(start=start, end=end),
- name="VEVENT"
- ),
- name="VCALENDAR"
- )).match(self.calendar):
+ if has != queryfilter.Filter(
+ Filter(
+ ComponentFilter(
+ ComponentFilter(
+ TimeRange(start=start, end=end),
+ name="VEVENT"
+ ),
+ name="VCALENDAR"
+ )
+ )
+ ).match(self.calendar):
self.fail("Calendar has %sVEVENT with timerange %s?" % (no, (start, end)))
test_TimeRange.todo = "recurrence expansion"
Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/test/util.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -219,24 +219,38 @@
self._properties = {}
self.resource = _FauxResource()
- def get(self, qname):
- data = self._properties.get(qname)
+ def get(self, qname, uid=None):
+ qnameuid = qname + (uid,)
+ data = self._properties.get(qnameuid)
if data is None:
raise HTTPError(StatusResponse(404, "No such property"))
return data
- def set(self, property):
- self._properties[property.qname()] = property
+ def set(self, property, uid=None):
+ qnameuid = property.qname() + (uid,)
+ self._properties[qnameuid] = property
- def delete(self, qname):
+ def delete(self, qname, uid=None):
try:
- del self._properties[qname]
+ qnameuid = qname + (uid,)
+ del self._properties[qnameuid]
except KeyError:
pass
+ def contains(self, qname, uid=None):
+ qnameuid = qname + (uid,)
+ return qnameuid in self._properties
- def list(self):
- return self._properties.iterkeys()
+ def list(self, uid=None, filterByUID=True):
+ results = self._properties.iterkeys()
+ if filterByUID:
+ return [
+ (namespace, name)
+ for namespace, name, propuid in results
+ if propuid == uid
+ ]
+ else:
+ return results
class StubCacheChangeNotifier(object):
Modified: CalendarServer/trunk/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezoneservice.py 2010-04-07 19:59:00 UTC (rev 5441)
+++ CalendarServer/trunk/twistedcaldav/timezoneservice.py 2010-04-07 20:00:47 UTC (rev 5442)
@@ -28,10 +28,11 @@
from twext.web2.dav import davxml
from twext.web2.http import HTTPError
from twext.web2.http import Response
+from twext.web2.http import XMLResponse
from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
-from twext.web2.http import XMLResponse
+from twisted.internet.defer import succeed
from twistedcaldav import customxml
from twistedcaldav.customxml import calendarserver_namespace
@@ -72,8 +73,8 @@
),
)
- def resourceType(self):
- return davxml.ResourceType.timezones
+ def resourceType(self, request):
+ return succeed(davxml.ResourceType.timezones)
def isCollection(self):
return False
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100407/6d5bc383/attachment-0001.html>
More information about the calendarserver-changes
mailing list