[CalendarServer-changes] [9392] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed Jun 27 10:17:03 PDT 2012


Revision: 9392
          http://trac.macosforge.org/projects/calendarserver/changeset/9392
Author:   cdaboo at apple.com
Date:     2012-06-27 10:17:03 -0700 (Wed, 27 Jun 2012)
Log Message:
-----------
Supported extended free busy responses.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/caldavxml.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py
    CalendarServer/trunk/twistedcaldav/scheduling/caldav.py

Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -26,6 +26,7 @@
 """
 
 from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.timezone import PyCalendarTimezone
 
 from txdav.xml.element import registerElement, dav_namespace
 from txdav.xml.element import WebDAVElement, PCDATAElement
@@ -140,6 +141,21 @@
         """
         return iComponent.fromString(str(self))
 
+    def gettimezone(self):
+        """
+        Get the timezone to use. If none, return UTC timezone.
+
+        @return: the L{PyCalendarTimezone} derived from the VTIMEZONE or utc.
+        """
+        calendar = self.calendar()
+        if calendar is not None:
+            tz = calendar.gettimezone()
+            if tz is not None:
+                return tz
+
+        # Default to using utc tzinfo
+        return PyCalendarTimezone(utc=True)
+
     def valid(self):
         """
         Determine whether the content of this element is a valid single VTIMEZONE component.

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -724,6 +724,9 @@
     def __ne__(self, other):
         return not self.__eq__(other)
 
+    def __hash__(self):
+        return hash(self.principalUID())
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -816,6 +819,23 @@
         return self.principalURL()
 
     @inlineCallbacks
+    def isProxyFor(self, principal):
+        """
+        Determine whether this principal is a read-only or read-write proxy for the
+        specified principal. 
+        """
+        
+        read_uids = (yield self.proxyFor(False))
+        if principal in read_uids:
+            returnValue(True)
+
+        write_uids = (yield self.proxyFor(True))
+        if principal in write_uids:
+            returnValue(True)
+        
+        returnValue(False)
+
+    @inlineCallbacks
     def proxyFor(self, read_write, resolve_memberships=True):
 
         proxyFors = set()

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -960,11 +960,14 @@
 
         @param start: the L{PyCalendarDateTime} for the start of the range.
         @param end: the L{PyCalendarDateTime} for the end of the range.
-        @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
+        @param timezone: the L{Component} or L{PyCalendarTimezone} of the VTIMEZONE to use for floating/all-day.
         @return: the L{Component} for the new calendar with expanded instances.
         """
         
-        pytz = PyCalendarTimezone(tzid=timezone.propertyValue("TZID")) if timezone else None
+        if timezone is not None and isinstance(timezone, Component):
+            pytz = PyCalendarTimezone(tzid=timezone.propertyValue("TZID"))
+        else:
+            pytz = timezone
 
         # Create new calendar object with same properties as the original, but
         # none of the originals sub-components
@@ -1922,6 +1925,27 @@
 
         return None
 
+    def getExtendedFreeBusy(self):
+        """
+        Get the X-CALENDARSEREVR-EXTENDED-FREEBUSY value. Works on either a VCALENDAR or on a component.
+        
+        @return: the string value of the X-CALENDARSEREVR-EXTENDED-FREEBUSY property, or None
+        """
+        
+        # Extract appropriate sub-component if this is a VCALENDAR
+        if self.name() == "VCALENDAR":
+            for component in self.subcomponents():
+                if component.name() not in ignoredComponents:
+                    return component.getExtendedFreeBusy()
+        else:
+            try:
+                # Find the primary subcomponent
+                return self.propertyValue("X-CALENDARSEREVR-EXTENDED-FREEBUSY")
+            except InvalidICalendarDataError:
+                pass
+
+        return None
+
     def setParameterToValueForPropertyWithValue(self, paramname, paramvalue, propname, propvalue):
         """
         Add or change the parameter to the specified value on the property having the specified value.
@@ -2269,7 +2293,7 @@
                 
     def removeXComponents(self, keep_components=()):
         """
-        Remove all X- properties except the specified ones
+        Remove all X- components except the specified ones
         """
 
         for component in tuple(self.subcomponents()):

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -431,10 +431,19 @@
         yield fbcacher.set(key, entry)
 
 @inlineCallbacks
-def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
-                         excludeuid=None, organizer=None,
-                         organizerPrincipal=None, same_calendar_user=False,
-                         servertoserver=False):
+def generateFreeBusyInfo(
+    request,
+    calresource,
+    fbinfo,
+    timerange,
+    matchtotal,
+    excludeuid=None,
+    organizer=None,
+    organizerPrincipal=None,
+    same_calendar_user=False,
+    servertoserver=False,
+    event_details=None,
+):
     """
     Run a free busy report on the specified calendar collection
     accumulating the free busy info for later processing.
@@ -453,6 +462,7 @@
         being targeted.
     @param servertoserver: a C{bool} indicating whether we are doing a local or
         remote lookup request.
+    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
     """
 
     # First check the privilege on this collection
@@ -473,7 +483,7 @@
         useruid = userPrincipal.principalUID()
     else:
         useruid = ""
-            
+
     # Get the timezone property from the collection.
     has_prop = (yield calresource.hasProperty((caldav_namespace, "calendar-timezone"), request))
     if has_prop:
@@ -481,6 +491,13 @@
     else:
         tz = None
 
+    # Look for possible extended free busy information
+    do_event_details = False
+    if event_details is not None and organizer_principal is not None and userPrincipal is not None:
+         
+        # Check of organizer is a delegate of attendee
+        do_event_details = (yield organizer_principal.isProxyFor(userPrincipal))
+
     # Try cache
     resources = (yield FBCacheEntry.getCacheEntry(calresource, useruid, timerange)) if config.EnableFreeBusyCache else None
 
@@ -537,19 +554,8 @@
         request.extendedLogItems["fb-cached"] = request.extendedLogItems.get("fb-cached", 0) + 1
 
         # Determine appropriate timezone (UTC is the default)
-        tzinfo = None
-        if tz is not None:
-            calendar = tz.calendar()
-            if calendar is not None:
-                for subcomponent in calendar.subcomponents():
-                    if subcomponent.name() == "VTIMEZONE":
-                        # <filter> contains exactly one <comp-filter>
-                        tzinfo = subcomponent.gettimezone()
-                        break
+        tzinfo = tz.gettimezone() if tz is not None else PyCalendarTimezone(utc=True)
 
-        if tzinfo is None:
-            tzinfo = PyCalendarTimezone(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:
@@ -612,6 +618,12 @@
                 if matchtotal > max_number_of_matches:
                     raise NumberOfMatchesWithinLimits(max_number_of_matches)
                 
+                # Add extended details
+                if do_event_details:
+                    child = (yield request.locateChildResource(calresource, name))
+                    calendar = (yield child.iCalendarForUser(request))
+                    _addEventDetails(calendar, event_details, timerange, tzinfo)
+
         else:
             child = (yield request.locateChildResource(calresource, name))
             calendar = (yield child.iCalendarForUser(request))
@@ -652,9 +664,53 @@
                     processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                 else:
                     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 request.locateChildResource(calresource, name))
+                    calendar = (yield child.iCalendarForUser(request))
+                    _addEventDetails(calendar, event_details, timerange, tzinfo)
     
     returnValue(matchtotal)
 
+def _addEventDetails(calendar, event_details, timerange, tzinfo):
+    """
+    Expand events within the specified time range and limit the set of properties to those allowed for
+    delegate extended free busy.
+    
+    @param calendar: the calendar object to expand
+    @type calendar: L{Component}
+    @param event_details: list to append VEVENT components to
+    @type event_details: C{list}
+    @param timerange: the time-range in which to expand
+    @type timerange: L{TimeRange}
+    @param tzinfo: timezone for floating time calculations
+    @type tzinfo: L{PyCalendarTimezone}
+    """
+
+    # First expand the component
+    expanded = calendar.expand(timerange.start, timerange.end, timezone=tzinfo)
+
+    # Remove all but essential properties
+    expanded.filterProperties(keep=(
+        "UID",
+        "RECURRENCE-ID",
+        "DTSTAMP",
+        "DTSTART",
+        "DTEND",
+        "DURATION",
+        "SUMMARY",
+    ))
+
+    # Need to remove all child components of VEVENT
+    for subcomponent in expanded.subcomponents():
+        if subcomponent.name() == "VEVENT":
+            for sub in tuple(subcomponent.subcomponents()):
+                subcomponent.removeComponent(sub)
+
+    event_details.extend([subcomponent for subcomponent in expanded.subcomponents() if subcomponent.name() == "VEVENT"])
+
+
 def processEventFreeBusy(calendar, fbinfo, timerange, tzinfo):
     """
     Extract free busy data from a VEVENT component.
@@ -838,17 +894,19 @@
     normalizePeriodList(periods)
     return periods
 
-def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None):
+def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None, event_details=None):
     """
     Generate a VCALENDAR object containing a single VFREEBUSY that is the
     aggregate of the free busy info passed in.
-    @param fbinfo:    the array of busy periods to use.
-    @param timerange: the L{TimeRange} for the query.
-    @param organizer: the L{Property} for the Organizer of the free busy request, or None.
-    @param attendee:  the L{Property} for the Attendee responding to the free busy request, or None.
-    @param uid:       the UID value from the free busy request.
-    @param method:    the METHOD property value to insert.
-    @return:          the L{Component} containing the calendar data.
+
+    @param fbinfo:        the array of busy periods to use.
+    @param timerange:     the L{TimeRange} for the query.
+    @param organizer:     the L{Property} for the Organizer of the free busy request, or None.
+    @param attendee:      the L{Property} for the Attendee responding to the free busy request, or None.
+    @param uid:           the UID value from the free busy request.
+    @param method:        the METHOD property value to insert.
+    @param event_details: VEVENT components to add.
+    @return:              the L{Component} containing the calendar data.
     """
     
     # Merge overlapping time ranges in each fb info section
@@ -883,4 +941,8 @@
         uid = md5(str(fbcalendar) + str(time.time())).hexdigest()
         fb.addProperty(Property("UID", uid))
 
+    if event_details:
+        for vevent in event_details:
+            fbcalendar.addComponent(vevent)
+
     return fbcalendar

Modified: CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/query/calendarqueryfilter.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -115,21 +115,10 @@
         """
         assert tzelement is None or isinstance(tzelement, CalDAVTimeZoneElement)
 
-        if tzelement is not None:
-            calendar = tzelement.calendar()
-            if calendar is not None:
-                for subcomponent in calendar.subcomponents():
-                    if subcomponent.name() == "VTIMEZONE":
-                        # <filter> contains exactly one <comp-filter>
-                        tz = subcomponent.gettimezone()
-                        self.child.settzinfo(tz)
-                        return tz
+        tz = tzelement.gettimezone() if tzelement is not None else PyCalendarTimezone(utc=True)
+        self.child.settzinfo(tz)
+        return tz
 
-        # Default to using utc tzinfo
-        utc = PyCalendarTimezone(utc=True)
-        self.child.settzinfo(utc)
-        return utc
-
     def getmaxtimerange(self):
         """
         Get the date farthest into the future in any time-range elements

Modified: CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2012-06-27 17:10:17 UTC (rev 9391)
+++ CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2012-06-27 17:17:03 UTC (rev 9392)
@@ -131,7 +131,10 @@
 
             # Different behavior for free-busy vs regular invite
             if self.freebusy:
-                yield self.generateFreeBusyResponse(recipient, self.responses, organizerProp, organizerPrincipal, uid)
+                # Look for special delegate extended free-busy request
+                event_details = [] if self.scheduler.calendar.getExtendedFreeBusy() else None
+
+                yield self.generateFreeBusyResponse(recipient, self.responses, organizerProp, organizerPrincipal, uid, event_details)
             else:
                 yield self.generateResponse(recipient, self.responses)
 
@@ -206,7 +209,7 @@
         returnValue(True)
 
     @inlineCallbacks
-    def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid):
+    def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid, event_details):
 
         # Extract the ATTENDEE property matching current recipient from the calendar data
         cuas = recipient.principal.calendarUserAddresses()
@@ -222,6 +225,7 @@
                 uid,
                 attendeeProp,
                 remote,
+                event_details,
             ))
         except:
             log.err("Could not determine free busy information: %s" % (recipient.cuaddr,))
@@ -246,7 +250,7 @@
             returnValue(True)
     
     @inlineCallbacks
-    def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, organizerPrincipal, uid, attendeeProp, remote):
+    def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, organizerPrincipal, uid, attendeeProp, remote, event_details=None):
 
         # Find the current recipients calendar-free-busy-set
         fbset = (yield recipient.principal.calendarFreeBusyURIs(self.scheduler.request))
@@ -290,6 +294,7 @@
                 organizerPrincipal = organizerPrincipal,
                 same_calendar_user = same_calendar_user,
                 servertoserver=remote,
+                event_details=event_details,
             ))
     
         # Build VFREEBUSY iTIP reply for this recipient
@@ -299,7 +304,8 @@
             organizer = organizerProp,
             attendee = attendeeProp,
             uid = uid,
-            method = "REPLY"
+            method = "REPLY",
+            event_details=event_details,
         )
 
         returnValue(fbresult)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120627/60c5f964/attachment-0001.html>


More information about the calendarserver-changes mailing list