<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14164] CalendarServer/trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14164">14164</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-11-13 11:56:48 -0800 (Thu, 13 Nov 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Client transp fix.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavstdconfigpy">CalendarServer/trunk/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstorebridgepy">CalendarServer/trunk/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_collectioncontentspy">CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_utilpy">CalendarServer/trunk/twistedcaldav/test/test_util.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavutilpy">CalendarServer/trunk/twistedcaldav/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingicaldiffpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingimplicitpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_icaldiffpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresqlpy">CalendarServer/trunk/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresql_externalpy">CalendarServer/trunk/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavicalendarstorepy">CalendarServer/trunk/txdav/caldav/icalendarstore.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoresqlpy">CalendarServer/trunk/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/stdconfig.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -868,6 +868,16 @@
</span><span class="cx"> "MaxPrincipalSearchReportResults": 500,
</span><span class="cx">
</span><span class="cx"> #
</span><ins>+ # Client fixes per user-agent match
+ #
+ "ClientFixes" : {
+ "ForceAttendeeTRANSP" : [
+ "iOS/8\\.0(\\..*)?",
+ "iOS/8\\.1(\\..*)?",
+ ],
+ },
+
+ #
</ins><span class="cx"> # Localization
</span><span class="cx"> #
</span><span class="cx"> "Localization" : {
</span><span class="lines">@@ -1488,6 +1498,19 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><ins>+def _updateClientFixes(configDict, reloading=False):
+ #
+ # Compile ClientFixes expressions for speed
+ #
+ try:
+ configDict.ClientFixesCompiled = {}
+ for key, expressions in configDict.ClientFixes.items():
+ configDict.ClientFixesCompiled[key] = [re.compile("^{}$".format(x)) for x in expressions]
+ except re.error, e:
+ raise ConfigurationError("Invalid regular expression in ClientFixes: %s" % (e,))
+
+
+
</ins><span class="cx"> def _updateLogLevels(configDict, reloading=False):
</span><span class="cx"> log.publisher.levels.clearLogLevels()
</span><span class="cx">
</span><span class="lines">@@ -1678,6 +1701,7 @@
</span><span class="cx"> _postUpdateAugmentService,
</span><span class="cx"> _updateACLs,
</span><span class="cx"> _updateRejectClients,
</span><ins>+ _updateClientFixes,
</ins><span class="cx"> _updateLogLevels,
</span><span class="cx"> _updateNotifications,
</span><span class="cx"> _updateICalendar,
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/storebridge.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/storebridge.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -48,7 +48,7 @@
</span><span class="cx"> from twistedcaldav.sharing import (
</span><span class="cx"> invitationBindStatusToXMLMap, invitationBindModeToXMLMap
</span><span class="cx"> )
</span><del>-from twistedcaldav.util import bestAcceptType
</del><ins>+from twistedcaldav.util import bestAcceptType, matchClientFixes
</ins><span class="cx"> from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.caldav.icalendarstore import (
</span><span class="lines">@@ -59,7 +59,7 @@
</span><span class="cx"> InvalidPerUserDataMerge,
</span><span class="cx"> AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation,
</span><span class="cx"> ShareeAllowedError, DuplicatePrivateCommentsError, InvalidSplit,
</span><del>- AttachmentSizeTooLarge, UnknownTimezone)
</del><ins>+ AttachmentSizeTooLarge, UnknownTimezone, SetComponentOptions)
</ins><span class="cx"> from txdav.carddav.iaddressbookstore import (
</span><span class="cx"> KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
</span><span class="cx"> )
</span><span class="lines">@@ -2848,8 +2848,18 @@
</span><span class="cx"> "Can't parse calendar data: %s" % (str(e),)
</span><span class="cx"> ))
</span><span class="cx">
</span><ins>+ # Look for client fixes
+ ua = request.headers.getHeader("User-Agent")
+ client_fix_transp = matchClientFixes(config, ua)
+
+ # Setup options
+ options = {
+ SetComponentOptions.smartMerge: schedule_tag_match,
+ SetComponentOptions.clientFixTRANSP: client_fix_transp,
+ }
+
</ins><span class="cx"> try:
</span><del>- response = (yield self.storeComponent(component, smart_merge=schedule_tag_match))
</del><ins>+ response = (yield self.storeComponent(component, options=options))
</ins><span class="cx"> except ResourceDeletedError:
</span><span class="cx"> # This is OK - it just means the server deleted the resource during the PUT. We make it look
</span><span class="cx"> # like the PUT succeeded.
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_collectioncontentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -48,7 +48,7 @@
</span><span class="cx"> _getFakeMemcacheProtocol)
</span><span class="cx">
</span><span class="cx"> # Need to not do implicit behavior during these tests
</span><del>- def _fakeDoImplicitScheduling(self, component, inserting, internal_state):
</del><ins>+ def _fakeDoImplicitScheduling(self, component, inserting, internal_state, options):
</ins><span class="cx"> return False, None, False, None
</span><span class="cx">
</span><span class="cx"> self.patch(CalendarObject, "doImplicitScheduling",
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_util.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_util.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/twistedcaldav/test/test_util.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -16,8 +16,10 @@
</span><span class="cx">
</span><span class="cx"> from txweb2.http_headers import Headers
</span><span class="cx">
</span><ins>+from twistedcaldav.config import ConfigDict
+from twistedcaldav.stdconfig import _updateClientFixes
+from twistedcaldav.util import bestAcceptType, userAgentProductTokens, matchClientFixes
</ins><span class="cx"> import twistedcaldav.test.util
</span><del>-from twistedcaldav.util import bestAcceptType
</del><span class="cx">
</span><span class="cx"> class AcceptType(twistedcaldav.test.util.TestCase):
</span><span class="cx"> """
</span><span class="lines">@@ -142,3 +144,78 @@
</span><span class="cx"> hdrs.addRawHeader(*hdr)
</span><span class="cx"> check = bestAcceptType(hdrs.getHeader("accept"), allowedTypes)
</span><span class="cx"> self.assertEqual(check, result, msg="Failed %s" % (title,))
</span><ins>+
+
+ def test_userAgentProductTokens(self):
+ """
+ Test that L{userAgentProductTokens} correctly parses a User-Agent header.
+ """
+ for hdr, result in (
+ # Valid syntax
+ ("Client/1.0", ["Client/1.0", ]),
+ ("Client/1.0 FooBar/2", ["Client/1.0", "FooBar/2", ]),
+ ("Client/1.0 (commentary here)", ["Client/1.0", ]),
+ ("Client/1.0 (FooBar/2)", ["Client/1.0", ]),
+ ("Client/1.0 (commentary here) FooBar/2", ["Client/1.0", "FooBar/2", ]),
+ ("Client/1.0 (commentary here) FooBar/2 (more commentary here) ", ["Client/1.0", "FooBar/2", ]),
+
+ # Invalid syntax
+ ("Client/1.0 (commentary here FooBar/2", ["Client/1.0", ]),
+ ("Client/1.0 commentary here) FooBar/2", ["Client/1.0", "commentary", "here)", "FooBar/2", ]),
+ ):
+ self.assertEqual(userAgentProductTokens(hdr), result, msg="Mismatch: {}".format(hdr))
+
+
+ def test_matchClientFixes(self):
+ """
+ Test that L{matchClientFixes} correctly identifies clients with matching fix tokens.
+ """
+ c = ConfigDict()
+ c.ClientFixes = {
+ "fix1": [
+ "Client/1\\.0.*",
+ "Client/1\\.1(\\..*)?",
+ "Client/2",
+ ],
+ "fix2": [
+ "Other/1\\.0.*",
+ ],
+ }
+ _updateClientFixes(c)
+ _updateClientFixes(c)
+
+ # Valid matches
+ for ua in (
+ "Client/1.0 FooBar/2",
+ "Client/1.0.1 FooBar/2",
+ "Client/1.0.1.1 FooBar/2",
+ "Client/1.1 FooBar/2",
+ "Client/1.1.1 FooBar/2",
+ "Client/2 FooBar/2",
+ ):
+ self.assertEqual(
+ matchClientFixes(c, ua),
+ set(("fix1",)),
+ msg="Did not match {}".format(ua),
+ )
+
+ # Valid non-matches
+ for ua in (
+ "Client/1 FooBar/2",
+ "Client/1.10 FooBar/2",
+ "Client/2.0 FooBar/2",
+ "Client/2.0.1 FooBar/2",
+ "Client FooBar/2",
+ "Client/3 FooBar/2",
+ "Client/3.0 FooBar/2",
+ "Client/10 FooBar/2",
+ "Client/10.0 FooBar/2",
+ "Client/10.0.1 FooBar/2",
+ "Client/10.0.1 (Client/1.0) FooBar/2",
+ "Client/10.0.1 (foo Client/1.0 bar) FooBar/2",
+ ):
+ self.assertEqual(
+ matchClientFixes(c, ua),
+ set(),
+ msg="Incorrectly matched {}".format(ua),
+ )
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/util.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/util.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/twistedcaldav/util.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -18,6 +18,7 @@
</span><span class="cx"> import re
</span><span class="cx"> import sys
</span><span class="cx"> import base64
</span><ins>+import itertools
</ins><span class="cx">
</span><span class="cx"> from subprocess import Popen, PIPE, STDOUT
</span><span class="cx"> from hashlib import md5, sha1
</span><span class="lines">@@ -541,3 +542,57 @@
</span><span class="cx"> result_qval = qval
</span><span class="cx">
</span><span class="cx"> return result
</span><ins>+
+
+
+def userAgentProductTokens(user_agent):
+ """
+ Parse an HTTP User-Agent header to extract the product tokens and ignore
+ any parenthesized comment strings in the header.
+
+ @param user_agent: text of User-Agent header value
+ @type user_agent: L{str}
+
+ @return: list of product tokens extracted from the header
+ @rtype: L{list}
+ """
+
+ ua_hdr = user_agent.split()
+ ua_tokens = []
+ comment = False
+ for token in ua_hdr:
+ if comment:
+ if token.endswith(")"):
+ comment = False
+ elif token.startswith("("):
+ if not token.endswith(")"):
+ comment = True
+ else:
+ ua_tokens.append(token)
+
+ return ua_tokens
+
+
+
+def matchClientFixes(config, user_agent):
+ """
+ Given a user-agent string, see if it matches any of the configured client fixes.
+
+ @param config: the L{config} to match against.
+ @type config: L{ConfigDict}
+ @param user_agent: the HTTP User-Agent header value to test.
+ @type user_agent: L{str}
+ """
+
+ if len(config.ClientFixesCompiled) == 0 or not user_agent:
+ return set()
+
+ ua_tokens = userAgentProductTokens(user_agent)
+
+ client_fixes = set()
+ for fix, patterns in config.ClientFixesCompiled.items():
+ for pattern, token in itertools.product(patterns, ua_tokens):
+ if pattern.match(token) is not None:
+ client_fixes.add(fix)
+ break
+ return client_fixes
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingicaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -27,10 +27,6 @@
</span><span class="cx"> from txdav.caldav.datastore.scheduling.utils import normalizeCUAddr
</span><span class="cx"> from txdav.caldav.datastore.scheduling.itip import iTipGenerator
</span><span class="cx">
</span><del>-"""
-Class that handles diff'ing two calendar objects.
-"""
-
</del><span class="cx"> __all__ = [
</span><span class="cx"> "iCalDiff",
</span><span class="cx"> ]
</span><span class="lines">@@ -38,19 +34,32 @@
</span><span class="cx"> log = Logger()
</span><span class="cx">
</span><span class="cx"> class iCalDiff(object):
</span><ins>+ """
+ This object is used for doing comparisons between two calendar objects to
+ work out what the changes are, in order to determine whether a scheduling
+ operation might be needed. The behavior will varying based on whether the
+ change is being triggered by an Organizer or an Attendee.
+ """
</ins><span class="cx">
</span><del>- def __init__(self, oldcalendar, newcalendar, smart_merge):
</del><ins>+ def __init__(self, oldcalendar, newcalendar, smart_merge, forceTRANSP=False):
</ins><span class="cx"> """
</span><ins>+ Note that this object will always duplicate the calendar objects when doing
+ comparisons so as not to change the calendar objects passed in.
</ins><span class="cx">
</span><del>- @param oldcalendar:
- @type oldcalendar:
- @param newcalendar:
- @type newcalendar:
</del><ins>+ @param oldcalendar: the existing calendar object to compare to
+ @type oldcalendar: L{Component}
+ @param newcalendar: the new calendar object
+ @type newcalendar: L{Component}
+ @param smart_merge: whether or not a "smart" CalDAV merge is done (If-Schedule-Tag-Match)
+ @type smart_merge: L{bool}
+ @param forceTRANSP: whether or not to apply a fix for clients failing to set TRANSP properly
+ @type forceTRANSP: L{bool}
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> self.oldcalendar = oldcalendar
</span><span class="cx"> self.newcalendar = newcalendar
</span><span class="cx"> self.smart_merge = smart_merge
</span><ins>+ self.forceTRANSP = forceTRANSP
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def organizerDiff(self):
</span><span class="lines">@@ -501,6 +510,13 @@
</span><span class="cx">
</span><span class="cx"> replyNeeded = True
</span><span class="cx">
</span><ins>+ # Apply client fix only if the PARTSTAT was changed
+ if self.forceTRANSP:
+ if clientAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION") in ("ACCEPTED", "TENTATIVE",):
+ clientComponent.replaceProperty(Property("TRANSP", "OPAQUE"))
+ else:
+ clientComponent.replaceProperty(Property("TRANSP", "TRANSPARENT"))
+
</ins><span class="cx"> if serverAttendee.parameterValue("RSVP", "FALSE") != clientAttendee.parameterValue("RSVP", "FALSE"):
</span><span class="cx"> if clientAttendee.parameterValue("RSVP", "FALSE") == "FALSE":
</span><span class="cx"> try:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingimplicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -37,6 +37,7 @@
</span><span class="cx"> from txdav.caldav.datastore.scheduling.utils import getCalendarObjectForRecord
</span><span class="cx"> from txdav.caldav.datastore.scheduling.work import ScheduleReplyWork, \
</span><span class="cx"> ScheduleReplyCancelWork, ScheduleOrganizerWork, ScheduleOrganizerSendWork
</span><ins>+from txdav.caldav.icalendarstore import SetComponentOptions
</ins><span class="cx">
</span><span class="cx"> import collections
</span><span class="cx">
</span><span class="lines">@@ -65,10 +66,11 @@
</span><span class="cx"> STATUS_ORPHANED_CANCELLED_EVENT = 1
</span><span class="cx"> STATUS_ORPHANED_EVENT = 2
</span><span class="cx">
</span><del>- def __init__(self, logItems=None):
</del><ins>+ def __init__(self, logItems=None, options=None):
</ins><span class="cx">
</span><span class="cx"> self.return_status = ImplicitScheduler.STATUS_OK
</span><span class="cx"> self.logItems = logItems
</span><ins>+ self.options = options
</ins><span class="cx"> self.allowed_to_schedule = True
</span><span class="cx"> self.suppress_refresh = False
</span><span class="cx">
</span><span class="lines">@@ -1683,7 +1685,11 @@
</span><span class="cx"> (caldav_namespace, "valid-attendee-change"),
</span><span class="cx"> "Cannot use an event when not listed as an attendee in the organizer's copy",
</span><span class="cx"> ))
</span><del>- differ = iCalDiff(oldcalendar, self.calendar, self.do_smart_merge)
</del><ins>+
+ # Check for a required client fix
+ forceTRANSP = SetComponentOptions.value(self.options, SetComponentOptions.clientFixTRANSP)
+
+ differ = iCalDiff(oldcalendar, self.calendar, self.do_smart_merge, forceTRANSP=forceTRANSP)
</ins><span class="cx"> return differ.attendeeMerge(self.attendee)
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_icaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -5248,6 +5248,391 @@
</span><span class="cx"> self.assertEqual(got_rescheduled, rescheduled, msg="%s expected rescheduled: '%s', got: '%s'" % (description, rescheduled, got_rescheduled,))
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def test_attendee_merge_forceTRANSP(self):
+
+ data = (
+ (
+ "#1.1 Simple component, no change",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.2 Simple component, change to ACCEPTED",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXXZ:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.3 Simple component, change to DECLINED",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=DECLINED;X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXXZ:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.4 Simple component, change to TENTATIVE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user2@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=TENTATIVE;X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXXZ:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.5 Simple component, change to ACCEPTED with TRANSP",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user2@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=TENTATIVE;X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXXZ:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.6 Simple component, remain as ACCEPTED with TRANSP",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.7 Simple component, remain as ACCEPTED change TRANSP",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.8 Simple component, ACCEPTED->DECLINED with TRANSP",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=DECLINED;X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXXZ:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ )
+
+ for description, calendar1, calendar2, attendee, result in data:
+ differ = iCalDiff(
+ Component.fromString(calendar1),
+ Component.fromString(calendar2),
+ False,
+ forceTRANSP=True,
+ )
+ diffResult = differ.attendeeMerge(attendee)
+ diffResult = (
+ diffResult[0],
+ diffResult[1],
+ tuple(sorted(diffResult[2])),
+ re.sub(
+ "X-CALENDARSERVER-DTSTAMP=[^Z]+",
+ "X-CALENDARSERVER-DTSTAMP=XXXXXXXXTXXXXXX",
+ str(diffResult[3]).replace("\r", "").replace("\n ", "")
+ ) if diffResult[3] else None,
+ )
+ self.assertEqual(diffResult, result, msg="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") for i in diffResult]),))
+
+
</ins><span class="cx"> def test_organizer_smart_merge(self):
</span><span class="cx">
</span><span class="cx"> data1 = (
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -78,7 +78,7 @@
</span><span class="cx"> InvalidDefaultCalendar, \
</span><span class="cx"> InvalidAttachmentOperation, DuplicatePrivateCommentsError, \
</span><span class="cx"> TimeRangeUpperLimit, TimeRangeLowerLimit, InvalidSplit, \
</span><del>- AttachmentSizeTooLarge, UnknownTimezone
</del><ins>+ AttachmentSizeTooLarge, UnknownTimezone, SetComponentOptions
</ins><span class="cx"> from txdav.caldav.icalendarstore import QuotaExceeded
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx"> CommonObjectResource, ECALENDARTYPE, SharingInvitation
</span><span class="lines">@@ -3041,7 +3041,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def doImplicitScheduling(self, component, inserting, internal_state, split_details=None):
</del><ins>+ def doImplicitScheduling(self, component, inserting, internal_state, options, split_details=None):
</ins><span class="cx">
</span><span class="cx"> new_component = None
</span><span class="cx"> did_implicit_action = False
</span><span class="lines">@@ -3063,7 +3063,7 @@
</span><span class="cx"> user_uuid = self._parentCollection.viewerHome().uid()
</span><span class="cx"> component = PerUserDataFilter(user_uuid).filter(component.duplicate())
</span><span class="cx">
</span><del>- scheduler = ImplicitScheduler(logItems=self._txn.logItems)
</del><ins>+ scheduler = ImplicitScheduler(logItems=self._txn.logItems, options=options)
</ins><span class="cx">
</span><span class="cx"> # PUT
</span><span class="cx"> do_implicit_action, is_scheduling_resource = (yield scheduler.testImplicitSchedulingPUT(
</span><span class="lines">@@ -3200,7 +3200,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def setComponent(self, component, inserting=False, smart_merge=False):
</del><ins>+ def setComponent(self, component, inserting=False, options=None):
</ins><span class="cx"> """
</span><span class="cx"> Public api for storing a component. This will do full data validation checks on the specified component.
</span><span class="cx"> Scheduling will be done automatically.
</span><span class="lines">@@ -3213,7 +3213,7 @@
</span><span class="cx"> except InvalidICalendarDataError as e:
</span><span class="cx"> raise InvalidComponentForStoreError(str(e))
</span><span class="cx"> try:
</span><del>- result = yield self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, smart_merge)
</del><ins>+ result = yield self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, options)
</ins><span class="cx"> except Exception:
</span><span class="cx"> ex = Failure()
</span><span class="cx">
</span><span class="lines">@@ -3228,7 +3228,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False, split_details=None):
</del><ins>+ def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, options=None, split_details=None):
</ins><span class="cx"> """
</span><span class="cx"> Setting the component internally to the store itself. This will bypass a whole bunch of data consistency checks
</span><span class="cx"> on the assumption that those have been done prior to the component data being provided, provided the flag is set.
</span><span class="lines">@@ -3236,7 +3236,11 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> self._componentChanged = False
</span><del>- self.schedule_tag_match = not self.calendar().isInbox() and internal_state == ComponentUpdateState.NORMAL and smart_merge
</del><ins>+ self.schedule_tag_match = (
+ not self.calendar().isInbox() and
+ internal_state == ComponentUpdateState.NORMAL and
+ SetComponentOptions.value(options, SetComponentOptions.smartMerge)
+ )
</ins><span class="cx"> schedule_state = None
</span><span class="cx">
</span><span class="cx"> if internal_state in (ComponentUpdateState.SPLIT_OWNER, ComponentUpdateState.SPLIT_ATTENDEE,):
</span><span class="lines">@@ -3255,7 +3259,7 @@
</span><span class="cx">
</span><span class="cx"> # Do scheduling only for owner split
</span><span class="cx"> if internal_state == ComponentUpdateState.SPLIT_OWNER:
</span><del>- yield self.doImplicitScheduling(component, inserting, internal_state, split_details)
</del><ins>+ yield self.doImplicitScheduling(component, inserting, internal_state, options, split_details)
</ins><span class="cx">
</span><span class="cx"> self.isScheduleObject = True
</span><span class="cx"> self.processScheduleTags(component, inserting, internal_state)
</span><span class="lines">@@ -3291,7 +3295,7 @@
</span><span class="cx"> yield self.decorateHostedStatus(component)
</span><span class="cx">
</span><span class="cx"> # Do scheduling
</span><del>- implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state))
</del><ins>+ implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state, options))
</ins><span class="cx"> if isinstance(implicit_result, int):
</span><span class="cx"> if implicit_result == ImplicitScheduler.STATUS_ORPHANED_CANCELLED_EVENT:
</span><span class="cx"> raise ResourceDeletedError("Resource created but immediately deleted by the server.")
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql_external.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql_external.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql_external.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -171,7 +171,7 @@
</span><span class="cx"> raise AssertionError("CalendarObjectExternal: not supported")
</span><span class="cx">
</span><span class="cx">
</span><del>- def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False, split_details=None):
</del><ins>+ def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, options=None, split_details=None):
</ins><span class="cx"> raise AssertionError("CalendarObjectExternal: not supported")
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavicalendarstorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/icalendarstore.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -950,3 +950,30 @@
</span><span class="cx"> NORMAL.description = "normal"
</span><span class="cx"> NORMAL_NO_IMPLICIT.description = "normal-no-implicit"
</span><span class="cx"> INTERNAL.description = "internal"
</span><ins>+
+
+
+class SetComponentOptions(Names):
+ """
+ Constants for keys used in the L{ICalendarObject.setComponent} method's
+ C{options} dict. The definitions below define the constant key name and
+ the type used for the dict entry's value.
+
+ @cvar smartMerge: Apply CalDAV smart merge to data (If-Schedule-Tag-Match)
+ Value: L{bool}
+
+ @cvar clientFixTRANSP: Apply fix for clients not setting TRANSP.
+ Value: L{bool}
+ """
+
+ smartMerge = NamedConstant()
+ smartMerge.description = u"Smart Merge: CalDAV If-Schedule-Tag-Match behavior"
+ smartMerge.defaultValue = False
+
+ clientFixTRANSP = NamedConstant()
+ clientFixTRANSP.description = u"Fix for clients not setting TRANSP"
+ clientFixTRANSP.defaultValue = False
+
+ @staticmethod
+ def value(options, key):
+ return options.get(key, key.defaultValue) if options is not None else key.defaultValue
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/sql.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -2041,7 +2041,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def remove(self):
</del><ins>+ def remove(self, options=None):
</ins><span class="cx">
</span><span class="cx"> if self.owned():
</span><span class="cx"> yield self.unshare() # storebridge should already have done this
</span><span class="lines">@@ -2412,7 +2412,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def setComponent(self, component, inserting=False):
</del><ins>+ def setComponent(self, component, inserting=False, options=None):
</ins><span class="cx">
</span><span class="cx"> if isinstance(component, str) or isinstance(component, unicode):
</span><span class="cx"> component = self._componentClass.fromString(component)
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (14163 => 14164)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql.py        2014-11-13 19:56:11 UTC (rev 14163)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py        2014-11-13 19:56:48 UTC (rev 14164)
</span><span class="lines">@@ -7238,7 +7238,7 @@
</span><span class="cx"> self._locked = True
</span><span class="cx">
</span><span class="cx">
</span><del>- def setComponent(self, component, inserting=False):
</del><ins>+ def setComponent(self, component, inserting=False, options=None):
</ins><span class="cx"> raise NotImplementedError
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -7285,7 +7285,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def remove(self):
</del><ins>+ def remove(self, options=None):
</ins><span class="cx"> yield self._deleteQuery.on(self._txn, NoSuchObjectResourceError,
</span><span class="cx"> resourceID=self._resourceID)
</span><span class="cx"> yield self.properties()._removeResource()
</span></span></pre>
</div>
</div>
</body>
</html>