[CalendarServer-changes] [14688] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Apr 20 11:00:40 PDT 2015
Revision: 14688
http://trac.calendarserver.org//changeset/14688
Author: cdaboo at apple.com
Date: 2015-04-20 11:00:40 -0700 (Mon, 20 Apr 2015)
Log Message:
-----------
Re-factor free busy to push fbset loop lower down in the call stack.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/freebusyurl.py
CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/delivery.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_freebusy.py
CalendarServer/trunk/txdav/common/datastore/podding/store_api.py
CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py
Modified: CalendarServer/trunk/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/freebusyurl.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/twistedcaldav/freebusyurl.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -270,10 +270,12 @@
attendeeProp = Property("ATTENDEE", recipient.cuaddr)
timerange = Period(self.start, self.end)
- fbresult = (yield FreebusyQuery(
- organizer, None, recipient, attendeeProp, None,
- timerange, None, None,
- ).generateAttendeeFreeBusyResponse())
+ fbresult = yield FreebusyQuery(
+ organizer=organizer,
+ recipient=recipient,
+ attendeeProp=attendeeProp,
+ timerange=timerange,
+ ).generateAttendeeFreeBusyResponse()
response = Response()
response.stream = MemoryStream(str(fbresult))
Modified: CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_freebusy.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/twistedcaldav/method/report_freebusy.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -37,6 +37,7 @@
from twistedcaldav.util import bestAcceptType
from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
+from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
from txdav.caldav.datastore.scheduling.freebusy import FreebusyQuery
from txdav.xml import element as davxml
@@ -85,12 +86,11 @@
yield report_common.applyToCalendarCollections(self, request, request.uri, depth, getCalendarList, (caldavxml.ReadFreeBusy(),))
# Do the actual freebusy query against the set of matched calendars
+ principal = yield self.ownerPrincipal(request)
+ organizer = recipient = LocalCalendarUser(principal.canonicalCalendarUserAddress(), principal.record)
timerange = Period(timerange.start, timerange.end)
try:
- fbresult = (yield FreebusyQuery(
- None, None, None, None, None,
- timerange, None, None,
- ).generateAttendeeFreeBusyResponse(fbset=fbset, method=None))
+ fbresult = yield FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None)
except NumberOfMatchesWithinLimits:
log.error("Too many matching components in free-busy report")
raise HTTPError(ErrorResponse(
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/delivery.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/delivery.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/delivery.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -200,11 +200,17 @@
attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas)
try:
- fbresult = (yield FreebusyQuery(
- self.scheduler.organizer, organizerProp, recipient, attendeeProp, uid,
- self.scheduler.timeRange, self.scheduler.excludeUID, self.scheduler.logItems,
+ fbresult = yield FreebusyQuery(
+ organizer=self.scheduler.organizer,
+ organizerProp=organizerProp,
+ recipient=recipient,
+ attendeeProp=attendeeProp,
+ uid=uid,
+ timerange=self.scheduler.timeRange,
+ excludeUID=self.scheduler.excludeUID,
+ logItems=self.scheduler.logItems,
event_details=event_details,
- ).generateAttendeeFreeBusyResponse())
+ ).generateAttendeeFreeBusyResponse()
except Exception as e:
log.failure(
"Could not determine free busy information for recipient {cuaddr}",
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -103,7 +103,11 @@
FBInfo_mapper = {"BUSY": "busy", "BUSY-TENTATIVE": "tentative", "BUSY-UNAVAILABLE": "unavailable"}
FBInfo_index_mapper = {'B': "busy", 'T': "tentative", 'U': "unavailable"}
- def __init__(self, organizer, organizerProp, recipient, attendeeProp, uid, timeRange, excludeUID, logItems, accountingItems=None, event_details=None):
+ def __init__(
+ self,
+ organizer=None, organizerProp=None, recipient=None, attendeeProp=None,
+ uid=None, timerange=None, excludeUID=None, logItems=None, accountingItems=None, event_details=None
+ ):
"""
@param organizer: the organizer making the freebusy request
@@ -114,10 +118,13 @@
@type recipient: L{CalendarUser}
@param attendeeProp: iCalendar ATTENDEE property from the request
@type attendeeProp: L{Property}
+ @param authzuid: directory UID of the currently authenticated user (i.e., the one making
+ the actual free busy request - might be different from the organizer)
+ @type authzuid: L{str}
@param uid: iCalendar UID in the request
@type uid: L{str}
- @param timeRange: time range for freebusy request
- @type timeRange: L{Period}
+ @param timerange: time range for freebusy request
+ @type timerange: L{Period}
@param excludeUID: an iCalendar UID to exclude from busy results
@type excludeUID: L{str}
@param logItems: items to add to logging
@@ -132,7 +139,7 @@
self.recipient = recipient
self.attendeeProp = attendeeProp
self.uid = uid
- self.timerange = timeRange
+ self.timerange = timerange
self.excludeuid = excludeUID
self.logItems = logItems
self.accountingItems = accountingItems
@@ -145,8 +152,50 @@
else:
self.same_calendar_user = False
+ # May need organizer principal
+ self.organizer_record = self.organizer.record if self.organizer and self.organizer.hosted() else None
+ self.organizer_uid = self.organizer_record.uid if self.organizer_record else None
+ # Free busy is per-user
+ self.attendee_record = self.recipient.record if self.recipient and self.recipient.hosted() else None
+ self.attendee_uid = self.attendee_record.uid if self.attendee_record else None
+
+
@inlineCallbacks
+ def checkRichOptions(self, txn):
+ if not hasattr(self, "rich_options"):
+ # Look for possible extended free busy information
+ self.rich_options = {
+ "organizer": False,
+ "delegate": False,
+ "resource": False,
+ }
+ if self.event_details is not None and self.organizer_record is not None and self.attendee_record is not None:
+
+ # Get the principal of the authorized user which may be different from the organizer if a delegate of
+ # the organizer is making the request
+ authzuid = txn._authz_uid if txn is not None else None
+ if authzuid is not None and authzuid != self.organizer_uid:
+ authz_record = yield txn.directoryService().recordWithUID(authzuid.decode("utf-8"))
+ else:
+ self.authzuid = self.organizer_uid
+ authz_record = self.organizer_record
+
+ # Check if attendee is also the organizer or the delegate doing the request
+ if self.attendee_uid in (self.organizer_uid, authzuid):
+ self.rich_options["organizer"] = True
+
+ # Check if authorized user is a delegate of attendee
+ proxy = (yield authz_record.isProxyFor(self.attendee_record))
+ if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
+ self.rich_options["delegate"] = True
+
+ # Check if attendee is room or resource
+ if config.Scheduling.Options.RoomResourceRichFreeBusy and self.attendee_record.getCUType() in ("RESOURCE", "ROOM",):
+ self.rich_options["resource"] = True
+
+
+ @inlineCallbacks
def generateAttendeeFreeBusyResponse(self, fbset=None, method="REPLY"):
# First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
@@ -159,18 +208,13 @@
fbset = [calendar for calendar in fbset if calendar.isUsedForFreeBusy()]
# Process the availability property from the Inbox.
- availability = self.recipient.inbox.ownerHome().getAvailability()
- if availability is not None:
- self.processAvailabilityFreeBusy(availability, fbinfo)
+ if hasattr(self.recipient, "inbox"):
+ availability = self.recipient.inbox.ownerHome().getAvailability()
+ if availability is not None:
+ self.processAvailabilityFreeBusy(availability, fbinfo)
# Now process free-busy set calendars
- matchtotal = 0
- for calendar in fbset:
- matchtotal = (yield self.generateFreeBusyInfo(
- calendar,
- fbinfo,
- matchtotal,
- ))
+ yield self.generateFreeBusyInfo(fbset, fbinfo)
# Build VFREEBUSY iTIP reply for this recipient
fbresult = self.buildFreeBusyResult(fbinfo, method=method)
@@ -268,50 +312,63 @@
return periods
- def generateFreeBusyInfo(self, calresource, fbinfo, matchtotal):
+ @inlineCallbacks
+ def generateFreeBusyInfo(self, fbset, fbinfo, matchtotal=0):
"""
Get freebusy information for a calendar. Different behavior for internal vs external calendars.
See L{_internalGenerateFreeBusyInfo} for argument description.
"""
- # TODO: this method really should be moved to L{CalendarObject} so the internal/external pieces
- # can be split across L{CalendarObject} and L{CalendarObjectExternal}
- if calresource.external():
- return self._externalGenerateFreeBusyInfo(
- calresource,
+ # Split calendar set into internal/external items
+ fbset_internal = [calendar for calendar in fbset if not calendar.external()]
+ fbset_external = [calendar for calendar in fbset if calendar.external()]
+
+ # TODO: we should probably figure out how to run the internal and external ones in parallel
+ if fbset_external:
+ matchtotal += (yield self._externalGenerateFreeBusyInfo(
+ fbset_external,
fbinfo,
matchtotal,
- )
- else:
- return self._internalGenerateFreeBusyInfo(
- calresource,
+ ))
+
+ if fbset_internal:
+ matchtotal += (yield self._internalGenerateFreeBusyInfo(
+ fbset_internal,
fbinfo,
matchtotal,
- )
+ ))
+ returnValue(matchtotal)
+
@inlineCallbacks
- def _externalGenerateFreeBusyInfo(self, calresource, fbinfo, matchtotal):
+ def _externalGenerateFreeBusyInfo(self, fbset, fbinfo, matchtotal):
"""
Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass
any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data.
See L{_internalGenerateFreeBusyInfo} for argument description.
"""
- fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy(
- calresource, self.organizer.cuaddr, self.recipient.cuaddr, self.timerange,
- matchtotal, self.excludeuid, self.event_details
- )
- for i in range(3):
- fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]])
+ for calresource in fbset:
+ fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy(
+ calresource,
+ self.organizer.cuaddr if self.organizer else None,
+ self.recipient.cuaddr if self.recipient else None,
+ self.timerange,
+ matchtotal,
+ self.excludeuid,
+ self.event_details,
+ )
+ for i in range(3):
+ fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]])
returnValue(matchtotal)
@inlineCallbacks
def _internalGenerateFreeBusyInfo(
self,
- calresource,
+ fbset,
fbinfo,
matchtotal,
):
@@ -323,141 +380,34 @@
@param matchtotal: the running total for the number of matches.
"""
- # First check the privilege on this collection
- # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
- # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy
+ yield self.checkRichOptions(fbset[0]._txn)
- # May need organizer principal
- organizer_record = self.organizer.record if self.organizer and self.organizer.hosted() else None
- organizer_uid = organizer_record.uid if organizer_record else ""
+ calidmap = dict([(fbcalendar.id(), fbcalendar,) for fbcalendar in fbset])
+ directoryService = fbset[0].directoryService()
- # Free busy is per-user
- attendee_record = self.recipient.record if self.organizer and self.recipient.hosted() else None
- attendee_uid = attendee_record.uid if attendee_record else calresource.viewerHome().uid()
+ resources, tzinfos = yield self._matchResources(fbset)
- # Get the timezone property from the collection.
- tz = calresource.getTimezone()
-
- # Look for possible extended free busy information
- rich_options = {
- "organizer": False,
- "delegate": False,
- "resource": False,
- }
- do_event_details = False
- if self.event_details is not None and organizer_record is not None and attendee_record is not None:
-
- # Get the principal of the authorized user which may be different from the organizer if a delegate of
- # the organizer is making the request
- authz_uid = organizer_uid
- authz_record = organizer_record
- if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid:
- authz_uid = calresource._txn._authz_uid
- authz_record = yield calresource.directoryService().recordWithUID(authz_uid.decode("utf-8"))
-
- # Check if attendee is also the organizer or the delegate doing the request
- if attendee_uid in (organizer_uid, authz_uid):
- do_event_details = True
- rich_options["organizer"] = True
-
- # Check if authorized user is a delegate of attendee
- proxy = (yield authz_record.isProxyFor(attendee_record))
- if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
- do_event_details = True
- rich_options["delegate"] = True
-
- # Check if attendee is room or resource
- if config.Scheduling.Options.RoomResourceRichFreeBusy and attendee_record.getCUType() in ("RESOURCE", "ROOM",):
- do_event_details = True
- rich_options["resource"] = True
-
- # Try cache
- resources = (yield FBCacheEntry.getCacheEntry(calresource, attendee_uid, self.timerange)) if config.EnableFreeBusyCache else None
-
- if resources is None:
-
- if self.accountingItems is not None:
- self.accountingItems["fb-uncached"] = self.accountingItems.get("fb-uncached", 0) + 1
-
- caching = False
- if config.EnableFreeBusyCache:
- # Log extended item
- if self.logItems is not None:
- self.logItems["fb-uncached"] = self.logItems.get("fb-uncached", 0) + 1
-
- # We want to cache a large range of time based on the current date
- cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
- cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))
-
- # If the requested time range would fit in our allowed cache range, trigger the cache creation
- if compareDateTime(self.timerange.getStart(), cache_start) >= 0 and compareDateTime(self.timerange.getEnd(), cache_end) <= 0:
- cache_timerange = Period(cache_start, cache_end)
- caching = True
-
- #
- # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
- # We then take those results and merge them into one VFREEBUSY component
- # with appropriate FREEBUSY properties, and return that single item as iCal data.
- #
-
- # Create fake filter element to match time-range
- tr = TimeRange(
- start=(cache_timerange if caching else self.timerange).getStart().getText(),
- end=(cache_timerange if caching else self.timerange).getEnd().getText(),
- )
- filter = caldavxml.Filter(
- caldavxml.ComponentFilter(
- caldavxml.ComponentFilter(
- tr,
- name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
- ),
- name="VCALENDAR",
- )
- )
- filter = Filter(filter)
- tzinfo = filter.settimezone(tz)
- if self.accountingItems is not None:
- self.accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),)
-
- try:
- resources = yield calresource.search(filter, useruid=attendee_uid, fbtype=True)
- if caching:
- yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid, cache_timerange, resources)
- except IndexedSearchException:
- raise InternalDataStoreError("Invalid indexedSearch query")
-
- else:
- if self.accountingItems is not None:
- self.accountingItems["fb-cached"] = self.accountingItems.get("fb-cached", 0) + 1
-
- # Log extended item
- if self.logItems is not None:
- self.logItems["fb-cached"] = self.logItems.get("fb-cached", 0) + 1
-
- # Determine appropriate timezone (UTC is the default)
- tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)
-
# We care about separate instances for VEVENTs only
aggregated_resources = {}
- for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+ for calid, name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
if transp == 'T' and fbtype != '?':
fbtype = 'F'
- aggregated_resources.setdefault((name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))
+ aggregated_resources.setdefault((calid, name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))
if self.accountingItems is not None:
self.accountingItems["fb-resources"] = {}
for k, v in aggregated_resources.items():
- name, uid, type, test_organizer = k
+ calid, name, uid, type, test_organizer = k
self.accountingItems["fb-resources"][uid] = []
for float, start, end, fbtype in v:
fbstart = parseSQLTimestampToPyCalendar(start)
if float == 'Y':
- fbstart.setTimezone(tzinfo)
+ fbstart.setTimezone(tzinfos[calid])
else:
fbstart.setTimezone(Timezone(utc=True))
fbend = parseSQLTimestampToPyCalendar(end)
if float == 'Y':
- fbend.setTimezone(tzinfo)
+ fbend.setTimezone(tzinfos[calid])
else:
fbend.setTimezone(Timezone(utc=True))
self.accountingItems["fb-resources"][uid].append((
@@ -472,7 +422,9 @@
recordUIDCache = {}
for key in aggregated_resources.iterkeys():
- name, uid, type, test_organizer = key
+ calid, name, uid, type, test_organizer = key
+ calresource = calidmap[calid]
+ tzinfo = tzinfos[calid]
# Short-cut - if an fbtype exists we can use that
if type == "VEVENT" and aggregated_resources[key][0][3] != '?':
@@ -486,25 +438,9 @@
continue
# Ignore ones of this UID
- if self.excludeuid:
- # See if we have a UID match
- if (self.excludeuid == uid):
- if test_organizer:
- test_uid = recordUIDCache.get(test_organizer)
- if test_uid is None:
- test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer))
- test_uid = test_record.uid if test_record else ""
- recordUIDCache[test_organizer] = test_uid
- else:
- test_uid = ""
+ if (yield self._testIgnoreExcludeUID(uid, test_organizer, recordUIDCache, directoryService)):
+ continue
- # Check that ORGANIZER's match (security requirement)
- if (self.organizer is None) or (organizer_uid == test_uid):
- continue
- # Check for no ORGANIZER and check by same calendar user
- elif (test_uid == "") and self.same_calendar_user:
- continue
-
# Apply a timezone to any floating times
fbstart = parseSQLTimestampToPyCalendar(start)
if float == 'Y':
@@ -532,12 +468,12 @@
raise QueryMaxResources(config.MaxQueryWithDataResults, matchtotal)
# Add extended details
- if do_event_details:
+ if any(self.rich_options.values()):
child = (yield calresource.calendarObjectWithName(name))
# Only add fully public events
if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
calendar = (yield child.componentForUser())
- self._addEventDetails(calendar, rich_options, tzinfo)
+ self._addEventDetails(calendar, self.rich_options, tzinfo)
else:
child = (yield calresource.calendarObjectWithName(name))
@@ -551,26 +487,9 @@
continue
# Ignore ones of this UID
- if self.excludeuid:
- # See if we have a UID match
- if (self.excludeuid == uid):
- test_organizer = calendar.getOrganizer()
- if test_organizer:
- test_uid = recordUIDCache.get(test_organizer)
- if test_uid is None:
- test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer))
- test_uid = test_record.uid if test_record else ""
- recordUIDCache[test_organizer] = test_uid
- else:
- test_uid = ""
+ if (yield self._testIgnoreExcludeUID(uid, calendar.getOrganizer(), recordUIDCache, calresource.directoryService())):
+ continue
- # Check that ORGANIZER's match (security requirement)
- if (self.organizer is None) or (organizer_uid == test_uid):
- continue
- # Check for no ORGANIZER and check by same calendar user
- elif (test_organizer is None) and self.same_calendar_user:
- continue
-
if self.accountingItems is not None:
self.accountingItems.setdefault("fb-filter-match", []).append(uid)
@@ -593,16 +512,146 @@
assert "Free-busy query returned unwanted component: %s in %r", (name, calresource,)
# Add extended details
- if calendar.mainType() == "VEVENT" and do_event_details:
- child = (yield calresource.calendarObjectWithName(name))
+ if calendar.mainType() == "VEVENT" and any(self.rich_options.values()):
# Only add fully public events
if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
- calendar = (yield child.componentForUser())
- self._addEventDetails(calendar, rich_options, tzinfo)
+ self._addEventDetails(calendar, self.rich_options, tzinfo)
returnValue(matchtotal)
+ @inlineCallbacks
+ def _matchResources(self, fbset):
+ """
+ For now iterate over each calendar and collect the results. In the longer term we might want to consider
+ doing a single DB query in the case where multiple calendars need to be searched.
+
+ @param fbset: list of calendars to process
+ @type fbset: L{list} of L{Calendar}
+ """
+
+ results = []
+ tzinfos = {}
+ for calresource in fbset:
+ resources, tzinfo = yield self._matchCalendarResources(calresource)
+ results.extend([(calresource.id(),) + tuple(resource) for resource in resources])
+ tzinfos[calresource.id()] = tzinfo
+
+ returnValue((results, tzinfos,))
+
+
+ @inlineCallbacks
+ def _matchCalendarResources(self, calresource):
+
+ # Get the timezone property from the collection.
+ tz = calresource.getTimezone()
+
+ # Try cache
+ resources = (yield FBCacheEntry.getCacheEntry(calresource, self.attendee_uid, self.timerange)) if config.EnableFreeBusyCache else None
+
+ if resources is None:
+
+ if self.accountingItems is not None:
+ self.accountingItems["fb-uncached"] = self.accountingItems.get("fb-uncached", 0) + 1
+
+ caching = False
+ if config.EnableFreeBusyCache:
+ # Log extended item
+ if self.logItems is not None:
+ self.logItems["fb-uncached"] = self.logItems.get("fb-uncached", 0) + 1
+
+ # We want to cache a large range of time based on the current date
+ cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
+ cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))
+
+ # If the requested time range would fit in our allowed cache range, trigger the cache creation
+ if compareDateTime(self.timerange.getStart(), cache_start) >= 0 and compareDateTime(self.timerange.getEnd(), cache_end) <= 0:
+ cache_timerange = Period(cache_start, cache_end)
+ caching = True
+
+ #
+ # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
+ # We then take those results and merge them into one VFREEBUSY component
+ # with appropriate FREEBUSY properties, and return that single item as iCal data.
+ #
+
+ # Create fake filter element to match time-range
+ tr = TimeRange(
+ start=(cache_timerange if caching else self.timerange).getStart().getText(),
+ end=(cache_timerange if caching else self.timerange).getEnd().getText(),
+ )
+ filter = caldavxml.Filter(
+ caldavxml.ComponentFilter(
+ caldavxml.ComponentFilter(
+ tr,
+ name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+ ),
+ name="VCALENDAR",
+ )
+ )
+ filter = Filter(filter)
+ tzinfo = filter.settimezone(tz)
+ if self.accountingItems is not None:
+ self.accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),)
+
+ try:
+ resources = yield calresource.search(filter, useruid=self.attendee_uid, fbtype=True)
+ if caching:
+ yield FBCacheEntry.makeCacheEntry(calresource, self.attendee_uid, cache_timerange, resources)
+ except IndexedSearchException:
+ raise InternalDataStoreError("Invalid indexedSearch query")
+
+ else:
+ if self.accountingItems is not None:
+ self.accountingItems["fb-cached"] = self.accountingItems.get("fb-cached", 0) + 1
+
+ # Log extended item
+ if self.logItems is not None:
+ self.logItems["fb-cached"] = self.logItems.get("fb-cached", 0) + 1
+
+ # Determine appropriate timezone (UTC is the default)
+ tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)
+
+ returnValue((resources, tzinfo,))
+
+
+ @inlineCallbacks
+ def _testIgnoreExcludeUID(self, uid, test_organizer, recordUIDCache, dirservice):
+ """
+ Check whether the event with the specified UID can be correctly excluded from the
+ freebusy result.
+
+ @param uid: UID to test
+ @type uid: L{str}
+ @param test_organizer: organizer cu-address of the event
+ @type test_organizer: L{str}
+ @param recordUIDCache: cache of directory records
+ @type recordUIDCache: L{dict}
+ @param dirservice: directory service to use for record lookups
+ @type dirservice: L{DirectoryService}
+ """
+
+ # See if we have a UID match
+ if self.excludeuid == uid:
+ if test_organizer:
+ test_uid = recordUIDCache.get(test_organizer)
+ if test_uid is None:
+ test_record = (yield dirservice.recordWithCalendarUserAddress(test_organizer))
+ test_uid = test_record.uid if test_record else ""
+ recordUIDCache[test_organizer] = test_uid
+ else:
+ test_uid = ""
+
+ # Check that ORGANIZER's match (security requirement)
+ if (self.organizer is None) or (self.organizer_uid == test_uid):
+ returnValue(True)
+ # Check for no ORGANIZER and check by same calendar user
+ elif (test_uid == "") and self.same_calendar_user:
+ returnValue(True)
+
+ returnValue(False)
+
+
def _addEventDetails(self, calendar, rich_options, tzinfo):
"""
Expand events within the specified time range and limit the set of properties to those allowed for
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -801,7 +801,7 @@
uid = calendar.resourceUID()
# Object to do freebusy query
- freebusy = FreebusyQuery(None, None, None, None, None, None, uid, None, accountingItems=accounting if len(instances) == 1 else None)
+ freebusy = FreebusyQuery(recipient=self.recipient, excludeUID=uid, accountingItems=accounting if len(instances) == 1 else None)
# Now compare each instance time-range with the index and see if there is an overlap
fbset = (yield self.recipient.inbox.ownerHome().loadCalendars())
@@ -844,7 +844,7 @@
)
freebusy.timerange = tr
- yield freebusy.generateFreeBusyInfo(testcal, fbinfo, 0)
+ yield freebusy.generateFreeBusyInfo([testcal, ], fbinfo)
# If any fbinfo entries exist we have an overlap
if len(fbinfo.busy) or len(fbinfo.tentative) or len(fbinfo.unavailable):
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_freebusy.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_freebusy.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_freebusy.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -61,7 +61,7 @@
""",
),
(
- "#1.2 No busy time with organizer & attendee",
+ "#1.2 No busy time with organizerProp & attendeeProp",
FreebusyQuery.FBInfo([], [], []),
Period.parseText("20080601T000000Z/20080602T000000Z"),
Property("ORGANIZER", "mailto:user01 at example.com"),
@@ -230,8 +230,8 @@
),
)
- for description, fbinfo, timerange, organizer, attendee, event_details, calendar in data:
- freebusy = FreebusyQuery(None, organizer, None, attendee, None, timerange, None, None, event_details=event_details)
+ for description, fbinfo, timerange, organizerProp, attendeeProp, event_details, calendar in data:
+ freebusy = FreebusyQuery(organizerProp=organizerProp, attendeeProp=attendeeProp, timerange=timerange, event_details=event_details)
result = freebusy.buildFreeBusyResult(fbinfo)
self.assertEqual(normalizeiCalendarText(str(result)), calendar.replace("\n", "\r\n"), msg=description)
@@ -313,11 +313,11 @@
calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1"))
fbinfo = FreebusyQuery.FBInfo([], [], [])
- matchtotal = 0
timerange = Period(self.now, self.now_1D)
- freebusy = FreebusyQuery(None, None, None, None, None, timerange, None, None)
- result = (yield freebusy.generateFreeBusyInfo(calendar, fbinfo, matchtotal))
+ organizer = recipient = yield calendarUserFromCalendarUserAddress("mailto:user01 at example.com", self.transactionUnderTest())
+ freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange)
+ result = (yield freebusy.generateFreeBusyInfo([calendar, ], fbinfo))
self.assertEqual(result, 0)
self.assertEqual(len(fbinfo.busy), 0)
self.assertEqual(len(fbinfo.tentative), 0)
@@ -345,11 +345,11 @@
yield self._createCalendarObject(data, "user01", "test.ics")
calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1"))
fbinfo = FreebusyQuery.FBInfo([], [], [])
- matchtotal = 0
timerange = Period(self.now, self.now_1D)
- freebusy = FreebusyQuery(None, None, None, None, None, timerange, None, None)
- result = (yield freebusy.generateFreeBusyInfo(calendar, fbinfo, matchtotal))
+ organizer = recipient = yield calendarUserFromCalendarUserAddress("mailto:user01 at example.com", self.transactionUnderTest())
+ freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange)
+ result = (yield freebusy.generateFreeBusyInfo([calendar, ], fbinfo))
self.assertEqual(result, 1)
self.assertEqual(fbinfo.busy, [Period.parseText("%s/%s" % (self.now_12H.getText(), self.now_13H.getText(),)), ])
self.assertEqual(len(fbinfo.tentative), 0)
@@ -377,19 +377,13 @@
yield self._createCalendarObject(data, "user01", "test.ics")
calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1"))
fbinfo = FreebusyQuery.FBInfo([], [], [])
- matchtotal = 0
timerange = Period(self.now, self.now_1D)
event_details = []
- organizer = recipient = (yield calendarUserFromCalendarUserAddress("mailto:user01 at example.com", self.transactionUnderTest()))
-
- freebusy = FreebusyQuery(organizer, None, recipient, None, None, timerange, None, None, event_details=event_details)
+ organizer = recipient = yield calendarUserFromCalendarUserAddress("mailto:user01 at example.com", self.transactionUnderTest())
+ freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange, event_details=event_details)
freebusy.same_calendar_user = True
- result = (yield freebusy.generateFreeBusyInfo(
- calendar,
- fbinfo,
- matchtotal,
- ))
+ result = yield freebusy.generateFreeBusyInfo([calendar, ], fbinfo)
self.assertEqual(result, 1)
self.assertEqual(fbinfo.busy, [Period(self.now_12H, self.now_13H), ])
self.assertEqual(len(fbinfo.tentative), 0)
Modified: CalendarServer/trunk/txdav/common/datastore/podding/store_api.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/store_api.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/common/datastore/podding/store_api.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -85,7 +85,7 @@
txn, request, server = yield self._getRequestForStoreObject("freebusy", calresource, False)
request["organizer"] = organizer
- request["recipient"] = organizer
+ request["recipient"] = recipient
request["timerange"] = timerange.getText()
request["matchtotal"] = matchtotal
request["excludeuid"] = excludeuid
@@ -107,17 +107,21 @@
# Operate on the L{CommonHomeChild}
calresource, _ignore = yield self._getStoreObjectForRequest(txn, request)
- organizer = yield calendarUserFromCalendarUserAddress(request["organizer"], txn)
- recipient = yield calendarUserFromCalendarUserAddress(request["recipient"], txn)
+ organizer = yield calendarUserFromCalendarUserAddress(request["organizer"], txn) if request["organizer"] else None
+ recipient = yield calendarUserFromCalendarUserAddress(request["recipient"], txn) if request["recipient"] else None
freebusy = FreebusyQuery(
- organizer, None, recipient, None, None,
- Period.parseText(request["timerange"]), request["excludeuid"], None, event_details=request["event_details"])
+ organizer=organizer,
+ recipient=recipient,
+ timerange=Period.parseText(request["timerange"]),
+ excludeUID=request["excludeuid"],
+ event_details=request["event_details"],
+ )
fbinfo = FreebusyQuery.FBInfo([], [], [])
matchtotal = yield freebusy.generateFreeBusyInfo(
- calresource,
+ [calresource, ],
fbinfo,
- request["matchtotal"],
+ matchtotal=request["matchtotal"],
)
# Convert L{Period} objects to text for JSON response
Modified: CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py 2015-04-17 21:05:17 UTC (rev 14687)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py 2015-04-20 18:00:40 UTC (rev 14688)
@@ -931,8 +931,8 @@
timerange = Period(DateTime.parseText(fbstart), DateTime.parseText(fbend))
organizer = recipient = (yield calendarUserFromCalendarUserAddress("mailto:puser01 at example.com", self.theTransactionUnderTest(1)))
- freebusy = FreebusyQuery(organizer, None, recipient, None, None, timerange, None, None)
- matchtotal = (yield freebusy.generateFreeBusyInfo(shared, fbinfo, 0))
+ freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange)
+ matchtotal = (yield freebusy.generateFreeBusyInfo([shared, ], fbinfo))
self.assertEqual(matchtotal, 1)
self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ])
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150420/04eb9c2e/attachment-0001.html>
More information about the calendarserver-changes
mailing list