[CalendarServer-changes] [13786] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Jul 24 08:56:17 PDT 2014
Revision: 13786
http://trac.calendarserver.org//changeset/13786
Author: cdaboo at apple.com
Date: 2014-07-24 08:56:17 -0700 (Thu, 24 Jul 2014)
Log Message:
-----------
Don't update the TIME_RANGE table when there are no changes to it.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2014-07-24 15:55:09 UTC (rev 13785)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2014-07-24 15:56:17 UTC (rev 13786)
@@ -265,59 +265,59 @@
#
"BindAddresses": [], # List of IP addresses to bind to [empty = all]
"BindHTTPPorts": [], # List of port numbers to bind to for HTTP
- # [empty = same as "Port"]
+ # [empty = same as "Port"]
"BindSSLPorts": [], # List of port numbers to bind to for SSL
- # [empty = same as "SSLPort"]
+ # [empty = same as "SSLPort"]
"InheritFDs": [], # File descriptors to inherit for HTTP requests
- # (empty = don't inherit)
+ # (empty = don't inherit)
"InheritSSLFDs": [], # File descriptors to inherit for HTTPS requests
- # (empty = don't inherit)
+ # (empty = don't inherit)
"MetaFD": 0, # Inherited file descriptor to call recvmsg() on to
- # receive sockets (none = don't inherit)
+ # receive sockets (none = don't inherit)
"UseMetaFD": True, # Use a 'meta' FD, i.e. an FD to transmit other FDs
- # to slave processes.
+ # to slave processes.
"UseDatabase": True, # True: database; False: files
"TransactionTimeoutSeconds": 300, # Timeout transactions that take longer than
- # the specified number of seconds. Zero means
- # no timeouts. 5 minute default.
+ # the specified number of seconds. Zero means
+ # no timeouts. 5 minute default.
"DBType": "", # 2 possible values: empty, meaning 'spawn postgres
- # yourself', or 'postgres', meaning 'connect to a
- # postgres database as specified by the 'DSN'
- # configuration key. Will support more values in
- # the future.
+ # yourself', or 'postgres', meaning 'connect to a
+ # postgres database as specified by the 'DSN'
+ # configuration key. Will support more values in
+ # the future.
"SpawnedDBUser": "caldav", # The username to use when DBType is empty
"DBImportFile": "", # File path to SQL file to import at startup (includes schema)
"DSN": "", # Data Source Name. Used to connect to an external
- # database if DBType is non-empty. Format varies
- # depending on database type.
+ # database if DBType is non-empty. Format varies
+ # depending on database type.
"DBAMPFD": 0, # Internally used by database to tell slave
- # processes to inherit a file descriptor and use it
- # as an AMP connection over a UNIX socket; see
- # twext.enterprise.adbapi2.ConnectionPoolConnection
+ # processes to inherit a file descriptor and use it
+ # as an AMP connection over a UNIX socket; see
+ # twext.enterprise.adbapi2.ConnectionPoolConnection
"SharedConnectionPool": False, # Use a shared database connection pool in
- # the master process, rather than having
- # each client make its connections directly.
+ # the master process, rather than having
+ # each client make its connections directly.
"FailIfUpgradeNeeded": True, # Set to True to prevent the server or utility
# tools from running if the database needs a schema
# upgrade.
"StopAfterUpgradeTriggerFile": "stop_after_upgrade", # if this file exists in ConfigRoot, stop
- # the service after finishing upgrade phase
+ # the service after finishing upgrade phase
"UpgradeHomePrefix": "", # When upgrading, only upgrade homes where the owner UID starts with
- # with the specified prefix. The upgrade will only be partial and only
- # apply to upgrade pieces that affect entire homes. The upgrade will
- # need to be run again without this prefix set to complete the overall
- # upgrade.
+ # with the specified prefix. The upgrade will only be partial and only
+ # apply to upgrade pieces that affect entire homes. The upgrade will
+ # need to be run again without this prefix set to complete the overall
+ # upgrade.
#
# Work queue configuration information
@@ -652,21 +652,21 @@
},
"RestrictCalendarsToOneComponentType" : True, # Only allow calendars to be created with a single component type
- # If this is on, it will also trigger an upgrade behavior that will
- # split existing calendars into multiples based on component type.
- # If on, it will also cause new accounts to provision with separate
- # calendars for events and tasks.
+ # If this is on, it will also trigger an upgrade behavior that will
+ # split existing calendars into multiples based on component type.
+ # If on, it will also cause new accounts to provision with separate
+ # calendars for events and tasks.
"SupportedComponents" : [ # Set of supported iCalendar components
"VEVENT",
"VTODO",
- #"VPOLL",
+ # "VPOLL",
],
"ParallelUpgrades" : False, # Perform upgrades - currently only the
- # database -> filesystem migration - but in
- # the future, hopefully all relevant
- # upgrades - in parallel in subprocesses.
+ # database -> filesystem migration - but in
+ # the future, hopefully all relevant
+ # upgrades - in parallel in subprocesses.
"MergeUpgrades": False, # During the upgrade phase of startup, rather than
# skipping homes found both on the filesystem and in
@@ -820,13 +820,13 @@
"Always" : False, # Override augments setting and always auto-schedule
"AllowUsers" : False, # Allow auto-schedule for users
"DefaultMode" : "automatic", # Default mode for auto-schedule processing, one of:
- # "none" - no auto-scheduling
- # "accept-always" - always accept, ignore busy time
- # "decline-always" - always decline, ignore free time
- # "accept-if-free" - accept if free, do nothing if busy
- # "decline-if-busy" - decline if busy, do nothing if free
- # "automatic" - accept if free, decline if busy
- "FutureFreeBusyDays" : 3 * 365, # How far into the future to check for booking conflicts
+ # "none" - no auto-scheduling
+ # "accept-always" - always accept, ignore busy time
+ # "decline-always" - always decline, ignore free time
+ # "accept-if-free" - accept if free, do nothing if busy
+ # "decline-if-busy" - decline if busy, do nothing if free
+ # "automatic" - accept if free, decline if busy
+ "FutureFreeBusyDays" : 3 * 365, # How far into the future to check for booking conflicts
},
"WorkQueues" : {
@@ -1008,29 +1008,29 @@
"Port": 11311,
"HandleCacheTypes": [
"Default",
-# "OpenDirectoryBacker",
-# "ImplicitUIDLock",
-# "RefreshUIDLock",
-# "DIGESTCREDENTIALS",
-# "resourceInfoDB",
-# "pubsubnodes",
-# "FBCache",
-# "ScheduleAddressMapper",
-# "SQL.props",
-# "SQL.calhome",
-# "SQL.adbkhome",
+ # "OpenDirectoryBacker",
+ # "ImplicitUIDLock",
+ # "RefreshUIDLock",
+ # "DIGESTCREDENTIALS",
+ # "resourceInfoDB",
+ # "pubsubnodes",
+ # "FBCache",
+ # "ScheduleAddressMapper",
+ # "SQL.props",
+ # "SQL.calhome",
+ # "SQL.adbkhome",
]
},
-# "Shared": {
-# "ClientEnabled": True,
-# "ServerEnabled": True,
-# "BindAddress": "127.0.0.1",
-# "Port": 11211,
-# "HandleCacheTypes": [
-# "ProxyDB",
-# "PrincipalToken",
-# ]
-# },
+ # "Shared": {
+ # "ClientEnabled": True,
+ # "ServerEnabled": True,
+ # "BindAddress": "127.0.0.1",
+ # "Port": 11211,
+ # "HandleCacheTypes": [
+ # "ProxyDB",
+ # "PrincipalToken",
+ # ]
+ # },
},
"memcached": "memcached", # Find in PATH
"MaxMemory": 0, # Megabytes
@@ -1110,6 +1110,7 @@
"FreeBusyIndexExpandAheadDays": 365,
"FreeBusyIndexExpandMaxDays": 5 * 365,
"FreeBusyIndexDelayedExpand": True,
+ "FreeBusyIndexSmartUpdate": True,
# The RootResource uses a twext property store. Specify the class here
"RootResourcePropStoreClass": "txweb2.dav.xattrprops.xattrPropertyStore",
@@ -1304,7 +1305,7 @@
# non-default values later.) -glyph
if previousAbsoluteName in configDict and (
configDict[previousAbsoluteName] == inDict[lastPath]
- ):
+ ):
userSpecifiedPath = configDict[previousRelativeName]
else:
userSpecifiedPath = inDict[lastPath]
@@ -1359,12 +1360,16 @@
maxConnections += configDict.MaxDBConnectionsPerPool
else:
# Otherwise the master *and* each worker process will be connecting
- maxConnections += ((configDict.MultiProcess.ProcessCount + 1) *
- configDict.MaxDBConnectionsPerPool)
+ maxConnections += (
+ (configDict.MultiProcess.ProcessCount + 1) *
+ configDict.MaxDBConnectionsPerPool
+ )
configDict.Postgres.MaxConnections = maxConnections
- configDict.Postgres.SharedBuffers = int(configDict.Postgres.MaxConnections *
- configDict.Postgres.BuffersToConnectionsRatio)
+ configDict.Postgres.SharedBuffers = int(
+ configDict.Postgres.MaxConnections *
+ configDict.Postgres.BuffersToConnectionsRatio
+ )
@@ -1622,8 +1627,7 @@
topic = getAPNTopicFromCertificate(certPath)
service[protocol]["Topic"] = topic
else:
- log.error("APNS certificate not found: %s" %
- (certPath,))
+ log.error("APNS certificate not found: %s" % (certPath,))
else:
log.error("APNS certificate path not specified")
@@ -1688,8 +1692,7 @@
pass
except KeychainPasswordNotFound:
# The password doesn't exist in the keychain.
- log.info("iMIP %s password not found in keychain" %
- (direction,))
+ log.info("iMIP %s password not found in keychain" % (direction,))
@@ -1755,7 +1758,7 @@
_preUpdateDirectoryService,
_preUpdateResourceService,
_preUpdateDirectoryAddressBookBackingDirectoryService,
- )
+)
POST_UPDATE_HOOKS = (
_updateMultiProcess,
_updateDataStore,
@@ -1773,7 +1776,7 @@
_updateSharing,
# _updateServers,
_updateCompliance,
- )
+)
def _cleanup(configDict, defaultDict):
cleanDict = copy.deepcopy(configDict)
@@ -1803,7 +1806,7 @@
del cleanDict[oldKey]
renamedOptions = {
-# "BindAddress": "BindAddresses",
+ # "BindAddress": "BindAddresses",
}
for key in configDict:
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py 2014-07-24 15:55:09 UTC (rev 13785)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py 2014-07-24 15:56:17 UTC (rev 13786)
@@ -346,7 +346,7 @@
# If smart_merge is happening, then derive an instance in the new data as the change in the old
# data is valid and likely due to some other attendee changing their status.
- if self.smart_merge:
+ if self.smart_merge:
newOverride = self.newcalendar.deriveInstance(rid, allowCancelled=True)
if newOverride is None:
self._logDiffError("attendeeMerge: Could not derive instance for uncancelled component: %s" % (key,))
@@ -426,7 +426,7 @@
# We used to generate a 403 here - but instead we now ignore this error and let the server data
# override the client
self._logDiffError("attendeeMerge: Mismatched calendar objects")
- #return False, False, (), None
+ # return False, False, (), None
changeCausesReply |= reply
if reply:
changedRids.append(rid)
@@ -646,8 +646,8 @@
duration = component.getProperty("DURATION")
timeRange = Period(
- start=dtstart.value() if dtstart is not None else None,
- end=dtend.value() if dtend is not None else None,
+ start=dtstart.value() if dtstart is not None else None,
+ end=dtend.value() if dtend is not None else None,
duration=duration.value() if duration is not None else None,
)
newdue = None
@@ -658,7 +658,7 @@
if dtstart or duration:
timeRange = Period(
- start=dtstart.value() if dtstart is not None else None,
+ start=dtstart.value() if dtstart is not None else None,
duration=duration.value() if duration is not None else None,
)
else:
@@ -737,7 +737,7 @@
return partstatChanged
- def whatIsDifferent(self):
+ def whatIsDifferent(self, isiTip=True):
"""
Compare the two calendar objects in their entirety and return a list of properties
and PARTSTAT parameters that are different.
@@ -766,7 +766,7 @@
for key in (oldset & newset):
component1 = oldmap[key]
component2 = newmap[key]
- self._diffComponents(component1, component2, rids)
+ self._diffComponents(component1, component2, rids, isiTip)
# Now verify that each additional component in oldset matches a derived component in newset
for key in oldset - newset:
@@ -774,7 +774,7 @@
newcomponent = self.newcalendar.deriveInstance(key[2])
if newcomponent is None:
continue
- self._diffComponents(oldcomponent, newcomponent, rids)
+ self._diffComponents(oldcomponent, newcomponent, rids, isiTip)
# Now verify that each additional component in oldset matches a derived component in newset
for key in newset - oldset:
@@ -782,11 +782,45 @@
if oldcomponent is None:
continue
newcomponent = newmap[key]
- self._diffComponents(oldcomponent, newcomponent, rids)
+ self._diffComponents(oldcomponent, newcomponent, rids, isiTip)
return rids
+ TRPROPS = frozenset((
+ "DTSTART",
+ "DTEND",
+ "DURATION",
+ "DUE",
+ "RECURRENCE-ID",
+ "RRULE",
+ "RDATE",
+ "EXDATE",
+ "STATUS",
+ "TRANSP",
+ "X-APPLE-TRAVEL-START",
+ "X-APPLE-TRAVEL-DURATION",
+ "X-APPLE-TRAVEL-RETURN",
+ "X-APPLE-TRAVEL-RETURN-DURATION",
+ ))
+
+ def timeRangeDifference(self):
+ """
+ Is there a difference between the two components that implies a change to the time or
+ transparency/status of any instance.
+
+ @return: L{True} if there is such a change, L{False} otherwise
+ @rtype: L{bool}
+ """
+
+ for props in self.whatIsDifferent(isiTip=False).values():
+ props = frozenset(props.keys())
+ if props & self.TRPROPS:
+ return True
+ else:
+ return False
+
+
def attendeeNeedsAction(self, diffs):
"""
Given a set of results from L{whatIsDifferent}, determine which recurrence-id's
@@ -856,20 +890,21 @@
return (date_changed_rids, recurrence_reschedule,)
- def _componentDuplicateAndNormalize(self, comp):
+ def _componentDuplicateAndNormalize(self, comp, isiTip=True):
comp = comp.duplicate()
comp.normalizePropertyValueLists("EXDATE")
- comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
- comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
- comp.removePropertyParameters("VOTER", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
comp.removeAlarms()
comp.normalizeAll()
comp.normalizeAttachments()
- iTipGenerator.prepareSchedulingMessage(comp, reply=True)
+ if isiTip:
+ comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
+ comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
+ comp.removePropertyParameters("VOTER", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
+ iTipGenerator.prepareSchedulingMessage(comp, reply=True)
return comp
- def _diffComponents(self, comp1, comp2, rids):
+ def _diffComponents(self, comp1, comp2, rids, isiTip=True):
assert isinstance(comp1, Component) and isinstance(comp2, Component)
@@ -878,8 +913,8 @@
return
# Duplicate then normalize for comparison
- comp1 = self._componentDuplicateAndNormalize(comp1)
- comp2 = self._componentDuplicateAndNormalize(comp2)
+ comp1 = self._componentDuplicateAndNormalize(comp1, isiTip)
+ comp2 = self._componentDuplicateAndNormalize(comp2, isiTip)
# Diff all the properties
propdiff = set(comp1.properties()) ^ set(comp2.properties())
@@ -888,7 +923,6 @@
propsChanged = {}
for prop in propdiff:
if prop.name() in (
- "TRANSP",
"DTSTAMP",
"CREATED",
"LAST-MODIFIED",
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2014-07-24 15:55:09 UTC (rev 13785)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2014-07-24 15:56:17 UTC (rev 13786)
@@ -164,7 +164,7 @@
self.recipient_calendar_resource = None
calendar_resource = (yield getCalendarObjectForRecord(self.txn, self.recipient.record, self.uid))
if calendar_resource:
- self.recipient_calendar = (yield calendar_resource.componentForUser(self.recipient.record.uid))
+ self.recipient_calendar = (yield calendar_resource.componentForUser(self.recipient.record.uid)).duplicate()
self.recipient_calendar_resource = calendar_resource
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-07-24 15:55:09 UTC (rev 13785)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-07-24 15:56:17 UTC (rev 13786)
@@ -59,6 +59,7 @@
from txdav.caldav.datastore.query.filter import Filter
from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
from txdav.caldav.datastore.scheduling.cuaddress import calendarUserFromCalendarUserAddress
+from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
from txdav.caldav.datastore.scheduling.utils import uidFromCalendarUserAddress
@@ -1490,9 +1491,10 @@
return Select(
[co.RESOURCE_NAME],
From=co,
- Where=((co.RECURRANCE_MIN > Parameter("minDate"))
- .Or(co.RECURRANCE_MAX < Parameter("maxDate")))
- .And(co.CALENDAR_RESOURCE_ID == Parameter("resourceID"))
+ Where=(
+ (co.RECURRANCE_MIN > Parameter("minDate"))
+ .Or(co.RECURRANCE_MAX < Parameter("maxDate"))
+ ).And(co.CALENDAR_RESOURCE_ID == Parameter("resourceID"))
)
@@ -1750,10 +1752,11 @@
resourceID=child._resourceID
)
+ # ===============================================================================
+ # Group sharing
+ # ===============================================================================
- #===============================================================================
- # Group sharing
- #===============================================================================
+
@inlineCallbacks
def reconcileGroupSharee(self, groupUID):
"""
@@ -1775,9 +1778,9 @@
[bind.HOME_RESOURCE_ID],
From=bind,
Where=(bind.CALENDAR_RESOURCE_ID == self._resourceID).And(
- (bind.BIND_MODE == _BIND_MODE_GROUP).Or(
- bind.BIND_MODE == _BIND_MODE_GROUP_READ).Or(
- bind.BIND_MODE == _BIND_MODE_GROUP_WRITE)
+ (bind.BIND_MODE == _BIND_MODE_GROUP)
+ .Or(bind.BIND_MODE == _BIND_MODE_GROUP_READ)
+ .Or(bind.BIND_MODE == _BIND_MODE_GROUP_WRITE)
)
)
)
@@ -1825,7 +1828,7 @@
rows = yield Select(
[Count(gs.GROUP_ID)],
From=gs,
- Where=(
+ Where=(
gs.GROUP_ID.In(
Select(
[gm.GROUP_ID],
@@ -1871,7 +1874,7 @@
rows = yield Select(
[Count(gs.GROUP_ID)],
From=gs,
- Where=(
+ Where=(
gs.GROUP_ID.In(
Select(
[gm.GROUP_ID],
@@ -1957,7 +1960,7 @@
rows = yield Select(
[Max(gs.GROUP_BIND_MODE)], # _BIND_MODE_WRITE > _BIND_MODE_READ
From=gs,
- Where=(
+ Where=(
gs.GROUP_ID.In(
Select(
[gm.GROUP_ID],
@@ -2224,7 +2227,7 @@
"_created",
"_modified",
"_dataversion",
- )
+ )
@property
@@ -2358,22 +2361,19 @@
if groupID in groupIDToMembershipHashMap:
if groupIDToMembershipHashMap[groupID] != membershipHash:
- yield Update({
- ga.MEMBERSHIP_HASH: membershipHash,
- },
+ yield Update(
+ {ga.MEMBERSHIP_HASH: membershipHash, },
Where=(ga.RESOURCE_ID == self._resourceID).And(
- ga.GROUP_ID == groupID
- )
+ ga.GROUP_ID == groupID)
).on(self._txn)
changed = True
del groupIDToMembershipHashMap[groupID]
else:
yield Insert({
- ga.RESOURCE_ID: self._resourceID,
- ga.GROUP_ID: groupID,
- ga.MEMBERSHIP_HASH: membershipHash,
- }
- ).on(self._txn)
+ ga.RESOURCE_ID: self._resourceID,
+ ga.GROUP_ID: groupID,
+ ga.MEMBERSHIP_HASH: membershipHash,
+ }).on(self._txn)
changed = True
if groupIDToMembershipHashMap:
@@ -2506,7 +2506,7 @@
"Attendee list size {0} is larger than allowed limit {1}".format(
attendeeListLength, config.MaxAttendeesPerInstance
)
- )
+ )
@inlineCallbacks
@@ -2523,13 +2523,15 @@
# Check for an allowed change
if organizer is None and (
cutype == "ROOM" and not config.Scheduling.Options.AllowLocationWithoutOrganizer or
- cutype == "RESOURCE" and not config.Scheduling.Options.AllowResourceWithoutOrganizer):
+ cutype == "RESOURCE" and not config.Scheduling.Options.AllowResourceWithoutOrganizer
+ ):
raise ValidOrganizerError("Organizer required in calendar data for a {0}".format(cutype.lower(),))
# Check for tracking the modifier
if organizer is None and (
cutype == "ROOM" and config.Scheduling.Options.TrackUnscheduledLocationData or
- cutype == "RESOURCE" and config.Scheduling.Options.TrackUnscheduledResourceData):
+ cutype == "RESOURCE" and config.Scheduling.Options.TrackUnscheduledResourceData
+ ):
# Find current principal and update modified by details
authz = yield self.directoryService().recordWithUID(self.calendar().viewerHome().authzuid().decode("utf-8"))
@@ -2588,7 +2590,7 @@
"X-CALENDARSERVER-ATTENDEE-COMMENT",
))
- if old_has_private_comments and not new_has_private_comments:
+ if old_has_private_comments and not new_has_private_comments and internal_state == ComponentUpdateState.NORMAL:
# Transfer old comments to new calendar
log.debug("Organizer private comment properties were entirely removed by the client. Restoring existing properties.")
old_calendar = (yield self.componentForUser())
@@ -2813,12 +2815,16 @@
"X-APPLE-RADIUS": "71",
"X-TITLE": title,
}
- structured = Property("X-APPLE-STRUCTURED-LOCATION",
+ structured = Property(
+ "X-APPLE-STRUCTURED-LOCATION",
geo.encode("utf-8"), params=params,
- valuetype=Value.VALUETYPE_URI)
+ valuetype=Value.VALUETYPE_URI
+ )
sub.replaceProperty(structured)
- newLocProperty = Property("LOCATION",
- "{0}\n{1}".format(title, street.encode("utf-8")))
+ newLocProperty = Property(
+ "LOCATION",
+ "{0}\n{1}".format(title, street.encode("utf-8"))
+ )
sub.replaceProperty(newLocProperty)
@@ -3125,6 +3131,14 @@
if did_implicit_action:
self._componentChanged = True
+ if not hasattr(self, "tr_change"):
+ if inserting or hasattr(component, "noInstanceIndexing") or not config.FreeBusyIndexSmartUpdate:
+ self.tr_change = None
+ else:
+ oldcomponent = yield self.componentForUser()
+ self.tr_change = iCalDiff(oldcomponent, component, False).timeRangeDifference()
+
+
# Always do the per-user data merge right before we store
component = (yield self.mergePerUserData(component, inserting))
@@ -3162,8 +3176,10 @@
category = ChangeCategory.inbox
elif internal_state == ComponentUpdateState.ORGANIZER_ITIP_UPDATE:
category = ChangeCategory.organizerITIPUpdate
- elif (internal_state == ComponentUpdateState.ATTENDEE_ITIP_UPDATE and
- hasattr(self._txn, "doing_attendee_refresh")):
+ elif (
+ internal_state == ComponentUpdateState.ATTENDEE_ITIP_UPDATE and
+ hasattr(self._txn, "doing_attendee_refresh")
+ ):
category = ChangeCategory.attendeeITIPUpdate
yield self._calendar.notifyChanged(category=category)
@@ -3201,7 +3217,12 @@
# In some cases there is no need to remove/rebuild the instance index because we know no time or
# freebusy related properties have changed (e.g. an attendee reply and refresh). In those cases
# the component will have a special attribute present to let us know to suppress the instance indexing.
- instanceIndexingRequired = not getattr(component, "noInstanceIndexing", False) or inserting or reCreate
+ if inserting or reCreate:
+ instanceIndexingRequired = True
+ elif getattr(component, "noInstanceIndexing", False):
+ instanceIndexingRequired = False
+ else:
+ instanceIndexingRequired = getattr(self, "tr_change", True) in (None, True)
instances = None
if instanceIndexingRequired:
@@ -3276,7 +3297,7 @@
# Now coerce indexing to off if needed
if not doInstanceIndexing:
- #instances = None # used by removeOldEventGroupLink() call at end
+ # instances = None # used by removeOldEventGroupLink() call at end
recurrenceLowerLimit = None
recurrenceLimit = DateTime(1900, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
@@ -3514,8 +3535,8 @@
self.log.error(
"Calendar data id={0} had unfixable problems:\n {1}".format(
self._resourceID, "\n ".join(unfixed),
- )
)
+ )
if fixed:
self.log.error(
@@ -3695,8 +3716,7 @@
txn = txn if txn is not None else self._txn
rMin, rMax = (
- yield self._recurrenceMinMaxByIDQuery.on(txn,
- resourceID=self._resourceID)
+ yield self._recurrenceMinMaxByIDQuery.on(txn, resourceID=self._resourceID)
)[0]
returnValue((
parseSQLDateToPyCalendar(rMin) if rMin is not None else None,
@@ -4535,7 +4555,7 @@
returnValue(None)
# Check it is still split-able
- will = (yield cobj.willSplit())
+ will = (yield cobj.willSplit())
if will:
# Now do the spitting
@@ -4606,8 +4626,7 @@
# prevented from committing successfully. It's not valid to have an
# attachment that doesn't point to a real file.
- home = (yield self._txn.calendarHomeWithResourceID(
- self._attachment._ownerHomeID))
+ home = (yield self._txn.calendarHomeWithResourceID(self._attachment._ownerHomeID))
oldSize = self._attachment.size()
newSize = self._file.tell()
@@ -4691,7 +4710,7 @@
att = schema.ATTACHMENT
if self._dropboxID:
where = (att.DROPBOX_ID == self._dropboxID).And(
- att.PATH == self._name)
+ att.PATH == self._name)
else:
where = (att.ATTACHMENT_ID == self._attachmentID)
rows = (yield Select(
@@ -5399,7 +5418,7 @@
yield Delete(
From=attco,
Where=(attco.ATTACHMENT_ID == self._attachmentID).And(
- attco.CALENDAR_OBJECT_RESOURCE_ID == resourceID),
+ attco.CALENDAR_OBJECT_RESOURCE_ID == resourceID),
).on(self._txn)
# References still exist - if not remove actual attachment
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2014-07-24 15:55:09 UTC (rev 13785)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2014-07-24 15:56:17 UTC (rev 13786)
@@ -50,7 +50,7 @@
from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
-from txdav.caldav.datastore.sql import CalendarStoreFeatures
+from txdav.caldav.datastore.sql import CalendarStoreFeatures, CalendarObject
from txdav.common.datastore.sql import ECALENDARTYPE, CommonObjectResource, \
CommonStoreTransactionMonitor
from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT, \
@@ -192,8 +192,7 @@
toHome = yield self.transactionUnderTest().calendarHomeWithUID(
"new-home", create=True)
toCalendar = yield toHome.calendarWithName("calendar")
- ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar,
- lambda x: x.component()))
+ ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component()))
self.assertEqual(ok, 1)
self.assertEqual(bad, 2)
@@ -212,8 +211,7 @@
toHome = yield self.transactionUnderTest().calendarHomeWithUID(
"new-home", create=True)
toCalendar = yield toHome.calendarWithName("calendar")
- ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar,
- lambda x: x.component()))
+ ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component()))
self.assertEqual(ok, 3)
self.assertEqual(bad, 0)
@@ -400,8 +398,7 @@
toHome = yield self.transactionUnderTest().calendarHomeWithUID(
"home_attachments", create=True)
toCalendar = yield toHome.calendarWithName("calendar")
- ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar,
- lambda x: x.component()))
+ ok, bad = (yield _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component()))
self.assertEqual(ok, 3)
self.assertEqual(bad, 0)
@@ -421,14 +418,14 @@
lambda x: x.component())
filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- caldavxml.TimeRange(start="%(now)s0201T000000Z" % self.nowYear, end="%(now)s0202T000000Z" % self.nowYear),
- name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
- ),
- name="VCALENDAR",
- )
- )
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ caldavxml.TimeRange(start="%(now)s0201T000000Z" % self.nowYear, end="%(now)s0202T000000Z" % self.nowYear),
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
filter = Filter(filter)
filter.settimezone(None)
@@ -640,8 +637,7 @@
@inlineCallbacks
def _defer1():
- yield cal1.createObjectResourceWithName("1.ics", Component.fromString(
-"""BEGIN:VCALENDAR
+ yield cal1.createObjectResourceWithName("1.ics", Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
CALSCALE:GREGORIAN
@@ -686,8 +682,7 @@
@inlineCallbacks
def _defer2():
- yield cal2.createObjectResourceWithName("2.ics", Component.fromString(
-"""BEGIN:VCALENDAR
+ yield cal2.createObjectResourceWithName("2.ics", Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
CALSCALE:GREGORIAN
@@ -768,7 +763,7 @@
txn2 = calendarStore.newTransaction()
notification_uid1_1 = yield txn1.notificationsWithUID(
- "uid1",
+ "uid1",
)
@inlineCallbacks
@@ -813,9 +808,11 @@
yield self.commit()
prop = schema.RESOURCE_PROPERTY
- _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
- From=prop,
- Where=prop.RESOURCE_ID == Parameter("resourceID"))
+ _allWithID = Select(
+ [prop.NAME, prop.VIEWER_UID, prop.VALUE],
+ From=prop,
+ Where=prop.RESOURCE_ID == Parameter("resourceID")
+ )
# Check that one property is present
home = yield self.homeUnderTest()
@@ -859,9 +856,11 @@
resourceID = calobject._resourceID
prop = schema.RESOURCE_PROPERTY
- _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
- From=prop,
- Where=prop.RESOURCE_ID == Parameter("resourceID"))
+ _allWithID = Select(
+ [prop.NAME, prop.VIEWER_UID, prop.VALUE],
+ From=prop,
+ Where=prop.RESOURCE_ID == Parameter("resourceID")
+ )
# No properties on existing calendar object
rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
@@ -912,9 +911,11 @@
yield self.commit()
prop = schema.RESOURCE_PROPERTY
- _allWithID = Select([prop.NAME, prop.VIEWER_UID, prop.VALUE],
- From=prop,
- Where=prop.RESOURCE_ID == Parameter("resourceID"))
+ _allWithID = Select(
+ [prop.NAME, prop.VIEWER_UID, prop.VALUE],
+ From=prop,
+ Where=prop.RESOURCE_ID == Parameter("resourceID")
+ )
# One property exists calendar object
rows = yield _allWithID.on(self.transactionUnderTest(), resourceID=resourceID)
@@ -1502,6 +1503,7 @@
# Re-add event with re-indexing
calendar = yield self.calendarUnderTest()
calendarObject = yield self.calendarObjectUnderTest(name="indexing.ics")
+ calendarObject.tr_change = True
yield calendarObject.setComponent(component)
instances2 = yield calendarObject.instances()
self.assertNotEqual(
@@ -1933,8 +1935,10 @@
rev = calendar._revisionsSchema
yield Delete(
From=rev,
- Where=(rev.HOME_RESOURCE_ID == Parameter("homeID")).And(
- rev.COLLECTION_NAME == Parameter("collectionName"))
+ Where=(
+ rev.HOME_RESOURCE_ID == Parameter("homeID")).And(
+ rev.COLLECTION_NAME == Parameter("collectionName")
+ )
).on(self.transactionUnderTest(), homeID=home.id(), collectionName="calendar")
yield self.commit()
@@ -2430,28 +2434,41 @@
""".replace("\n", "\r\n")
calendar = yield self.calendarUnderTest(name="calendar", home="user01")
- yield calendar.createCalendarObjectWithName("structured.ics",
- Component.fromString(data))
- cobj = yield self.calendarObjectUnderTest(name="structured.ics",
- calendar_name="calendar", home="user01")
+ yield calendar.createCalendarObjectWithName(
+ "structured.ics",
+ Component.fromString(data)
+ )
+ cobj = yield self.calendarObjectUnderTest(
+ name="structured.ics",
+ calendar_name="calendar",
+ home="user01"
+ )
comp = yield cobj.component()
components = list(comp.subcomponents())
# Check first component
locProp = components[0].getProperty("LOCATION")
- self.assertEquals(locProp.value(),
- "Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014")
+ self.assertEquals(
+ locProp.value(),
+ "Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014"
+ )
structProp = components[0].getProperty("X-APPLE-STRUCTURED-LOCATION")
- self.assertEquals(structProp.value(),
- "geo:37.331741,-122.030333")
+ self.assertEquals(
+ structProp.value(),
+ "geo:37.331741,-122.030333"
+ )
# Check second component
locProp = components[1].getProperty("LOCATION")
- self.assertEquals(locProp.value(),
- "Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014")
+ self.assertEquals(
+ locProp.value(),
+ "Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014"
+ )
structProp = components[1].getProperty("X-APPLE-STRUCTURED-LOCATION")
- self.assertEquals(structProp.value(),
- "geo:37.332633,-122.030502")
+ self.assertEquals(
+ structProp.value(),
+ "geo:37.332633,-122.030502"
+ )
yield self.commit()
@@ -2484,10 +2501,15 @@
self.patch(config.HostedStatus, "Enabled", True)
calendar = yield self.calendarUnderTest(name="calendar", home="user01")
- yield calendar.createCalendarObjectWithName("external.ics",
- Component.fromString(data))
- cobj = yield self.calendarObjectUnderTest(name="external.ics",
- calendar_name="calendar", home="user01")
+ yield calendar.createCalendarObjectWithName(
+ "external.ics",
+ Component.fromString(data)
+ )
+ cobj = yield self.calendarObjectUnderTest(
+ name="external.ics",
+ calendar_name="calendar",
+ home="user01"
+ )
comp = yield cobj.component()
components = list(comp.subcomponents())
@@ -6976,3 +6998,571 @@
self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future2) % relsubs, "Failed future: %s" % (title,))
self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past2) % relsubs, "Failed past: %s" % (title,))
self.assertEqual(normalize_iCalStr(ical_inbox), normalize_iCalStr(data_inbox2) % relsubs, "Failed inbox: %s" % (title,))
+
+
+
+class TimeRangeUpdateOptimization(CommonCommonTests, unittest.TestCase):
+ """
+ CalendarObject splitting tests
+ """
+
+ EVENT1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T130000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT4 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT5 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+STATUS:CANCELLED
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT6 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+X-APPLE-TRAVEL-DURATION;VALUE=DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT7 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT8 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT9 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+EXDATE:{now}T120000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ EVENT10 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+RDATE:{now}T150000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(TimeRangeUpdateOptimization, self).setUp()
+ yield self.buildStoreAndDirectory()
+ yield self.populate()
+
+ self.now = DateTime.getNowUTC()
+ self.now.setDateOnly(True)
+
+ self.trcount = 0
+ base_addInstances = CalendarObject._addInstances
+ def __addInstances(*args):
+ self.trcount += 1
+ return base_addInstances(*args)
+ self.patch(CalendarObject, "_addInstances", __addInstances)
+
+ self.patch(config, "FreeBusyIndexDelayedExpand", False)
+ self.patch(config, "FreeBusyIndexSmartUpdate", True)
+
+
+ @inlineCallbacks
+ def populate(self):
+ yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+ self.notifierFactory.reset()
+
+
+ @property
+ def requirements(self):
+ return {
+ "home1": {
+ "calendar_1": {},
+ },
+ "user01": {
+ "calendar": {},
+ "inbox": {},
+ },
+ "user02": {
+ "calendar": {},
+ "inbox": {},
+ },
+ }
+
+
+ @inlineCallbacks
+ def test_initalPUT(self):
+ """
+ Test that initial PUT causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withoutTRChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT does not cause T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withoutOptimization(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ self.patch(config, "FreeBusyIndexSmartUpdate", False)
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT does cause T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withTRChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT3.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withTranspChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT4.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withStatusChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT5.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withTravelTimeChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT6.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withRRULEChange(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT8.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withEXDATEAdd(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT9.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ @inlineCallbacks
+ def test_updatePUT_withRDATEAdd(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest()
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 1)
+
+ # Second PUT causes T-R change
+ cobj = yield self.calendarObjectUnderTest()
+ yield cobj.setComponent(Component.fromString(self.EVENT10.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 2)
+
+
+ INVITE1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+ INVITE2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+ INVITE3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+ INVITE4 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T140000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+ @inlineCallbacks
+ def test_schedulingPUT(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ # Need schedule-q off for this test
+ self.patch(config.Scheduling.Options.WorkQueues, "Enabled", False)
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.INVITE1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 3)
+
+ # Attendee reply does not cause T-R change (except for inbox item and attendee resource transp change)
+ cal = yield self.calendarUnderTest(home="user02", name="calendar")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 1)
+ yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 5)
+
+ # Organizer summary change does not cause T-R change (except for inbox item)
+ cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+ yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 6)
+
+ # Organizer dtstart change causes T-R change
+ cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+ yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 9)
+
+
+ @inlineCallbacks
+ def test_schedulingPUT_withoutOptimization(self):
+ """
+ Test that second PUT withe time change causes a TIME_RANGE update
+ """
+
+ self.patch(config, "FreeBusyIndexSmartUpdate", False)
+
+ # Need schedule-q off for this test
+ self.patch(config.Scheduling.Options.WorkQueues, "Enabled", False)
+
+ # First PUT causes T-R change
+ cal = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.INVITE1.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 3)
+
+ # Attendee reply does cause T-R change (except for organizer update)
+ cal = yield self.calendarUnderTest(home="user02", name="calendar")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 1)
+ yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 5)
+
+ # Organizer summary change causes T-R change
+ cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+ yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 8)
+
+ # Organizer dtstart change causes T-R change
+ cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+ yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
+ yield self.commit()
+
+ self.assertEqual(self.trcount, 11)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140724/853d458f/attachment-0001.html>
More information about the calendarserver-changes
mailing list