예제 #1
0
파일: itip.py 프로젝트: nunb/calendarserver
    def processRequest(itip_message, calendar, recipient):
        """
        Process a METHOD=REQUEST. We need to merge per-attendee properties such as TRANPS, COMPLETED etc
        with the data coming from the organizer.

        @param itip_message: the iTIP message to process.
        @type itip_message: L{Component}
        @param calendar: the calendar object to apply the REQUEST to
        @type calendar: L{Component}
        @param recipient: the attendee calendar user address to whom the message was sent
        @type recipient: C{str}

        @return: a C{tuple} of:
            calendar object ready to save, or C{None} (request should be ignored)
            a C{set} of recurrences that changed, or C{None}
        """

        # Check sequencing
        if not iTipProcessing.sequenceComparison(itip_message, calendar):
            # Ignore out of sequence message
            return None, None

        # Special check: if the SCHEDULE-AGENT is being changed throw away all the existing data
        if calendar.getOrganizerScheduleAgent() != itip_message.getOrganizerScheduleAgent():
            return (iTipProcessing.processNewRequest(itip_message, recipient, creating=True), {})

        # Merge Organizer data with Attendee's own changes (VALARMs, Comment only for now).
        from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
        differ = iCalDiff(calendar, itip_message, False)
        rids = differ.whatIsDifferent()
        needs_action_rids, reschedule = differ.attendeeNeedsAction(rids)

        # Different behavior depending on whether a master component is present or not
        # Here we cache per-attendee data from the existing master that we need to use in any new
        # overridden components that the organizer added
        current_master = calendar.masterComponent()
        if current_master:
            valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
            private_comments = current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
            transps = current_master.properties("TRANSP")
            completeds = current_master.properties("COMPLETED")
            organizer = current_master.getProperty("ORGANIZER")
            organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
            attendee = current_master.getAttendeeProperty((recipient,))
            attendee_dtstamp = attendee.parameterValue("X-CALENDARSERVER-DTSTAMP") if attendee else None
            other_props = {}
            for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
                props = tuple(current_master.properties(pname))
                if props:
                    other_props[pname] = props
        else:
            valarms = ()
            private_comments = ()
            transps = ()
            completeds = ()
            organizer_schedule_status = None
            attendee = None
            attendee_dtstamp = None
            other_props = {}

        if itip_message.masterComponent() is not None:

            # Get a new calendar object first
            new_calendar = iTipProcessing.processNewRequest(itip_message, recipient)

            # Copy over master alarms, comments etc
            master_component = new_calendar.masterComponent()
            transfer_partstat = None not in needs_action_rids and not reschedule
            seq_change = Component.compareComponentsForITIP(master_component, current_master, use_dtstamp=False) <= 0
            iTipProcessing._transferItems(master_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)

            # Now try to match recurrences in the new calendar
            for component in tuple(new_calendar.subcomponents()):
                if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
                    iTipProcessing.transferItems(calendar, component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)

            # Now try to match recurrences from the old calendar
            for component in calendar.subcomponents():
                if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
                    rid = component.getRecurrenceIDUTC()
                    if new_calendar.overriddenComponent(rid) is None:
                        allowCancelled = component.propertyValue("STATUS") == "CANCELLED"
                        hidden = component.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY)
                        new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled and not hidden)
                        if new_component is not None:
                            new_calendar.addComponent(new_component)
                            iTipProcessing.transferItems(calendar, new_component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
                            if hidden:
                                new_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))

            iTipProcessing.addTranspForNeedsAction(new_calendar.subcomponents(), recipient)

            # Replace the entire object
            return new_calendar, rids

        else:
            # Need existing tzids
            tzids = calendar.timezones()

            # Update existing instances
            for component in itip_message.subcomponents():
                if component.name() == "VTIMEZONE":
                    # May need to add a new VTIMEZONE
                    if component.propertyValue("TZID") not in tzids:
                        calendar.addComponent(component)
                else:
                    component = component.duplicate()
                    missingDeclined = iTipProcessing.transferItems(calendar, component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient, remove_matched=True)
                    if not missingDeclined:
                        calendar.addComponent(component)

            iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)

            # Write back the modified object
            return calendar, rids
예제 #2
0
    def sequenceComparison(itip, calendar):
        """
        Check the iTIP SEQUENCE values for the incoming iTIP message against the existing calendar data to determine
        whether the iTIP message is old and should be ignored.

        @param itip: the iTIP message to process
        @type itip: L{Component}
        @param calendar: the existing calendar data to compare with
        @type calendar: L{Component}

        @return: C{True} if the itip message is new and should be processed, C{False}
            if no processing is needed
        @rtype: C{bool}
        """

        # Master component comparison trumps all else
        itip_master = itip.masterComponent()
        cal_master = calendar.masterComponent()

        # If master component exists, compare all in iTIP and update if any are new
        if cal_master:
            for itip_component in itip.subcomponents():
                if itip_component.name() in ignoredComponents:
                    continue
                cal_component = calendar.overriddenComponent(itip_component.getRecurrenceIDUTC())
                if cal_component is None:
                    cal_component = cal_master

                # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
                # Treat components the same as meaning so an update - in theory no harm in doing that
                if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
                    return True

            return False

        elif itip_master:

            # Do comparison of each appropriate component if any one is new, process the itip
            for cal_component in calendar.subcomponents():
                if cal_component.name() in ignoredComponents:
                    continue
                itip_component = itip.overriddenComponent(cal_component.getRecurrenceIDUTC())
                if itip_component is None:
                    itip_component = itip_master

                # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
                # Treat components the same as meaning so an update - in theory no harm in doing that
                if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
                    return True

            return False

        else:
            # Do comparison of each matching component if any one is new, process the entire itip.
            # There is a race condition here, similar to REPLY, where we could reinstate an instance
            # that has been removed. Not much we can do about it without additional tracking.

            cal_rids = set()
            for cal_component in calendar.subcomponents():
                if cal_component.name() in ignoredComponents:
                    continue
                cal_rids.add(cal_component.getRecurrenceIDUTC())
            itip_rids = set()
            for itip_component in itip.subcomponents():
                if itip_component.name() in ignoredComponents:
                    continue
                itip_rids.add(itip_component.getRecurrenceIDUTC())

            # Compare ones that match
            for rid in cal_rids & itip_rids:
                cal_component = calendar.overriddenComponent(rid)
                itip_component = itip.overriddenComponent(rid)

                # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
                # Treat components the same as meaning so an update - in theory no harm in doing that
                if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
                    return True

            # If there are others in one set and not the other - always process, else no process
            return len(cal_rids ^ itip_rids) > 0
예제 #3
0
파일: itip.py 프로젝트: nunb/calendarserver
    def transferItems(from_calendar, to_component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient, remove_matched=False):
        """
        Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
        use the properties from that, or use the values provided as arguments (which have been derived from the original calendar's
        master component).

        @param from_calendar: the old calendar data to transfer items from
        @type from_calendar: L{Component}
        @param to_component: the new component to transfer items to
        @type to_component: L{Component}
        @param valarms: a C{list} of VALARM components from the old master to use
        @type valarms: C{list}
        @param private_comments: a C{list} of private comment properties from the old master to use
        @type private_comments: C{list}
        @param transps: a C{list} of TRANSP properties from the old master to use
        @type transps: C{list}
        @param completeds: a C{list} of COMPLETED properties from the old master to use
        @type completeds: C{list}
        @param organizer_schedule_status: a the SCHEDULE-STATUS value for the organizer from the old master to use
        @type organizer_schedule_status: C{str}
        @param attendee_dtstamp: an the ATTENDEE DTSTAMP parameter value from the old master to use
        @type attendee_dtstamp: C{str}
        @param other_props: other properties from the old master to use
        @type other_props: C{list}
        @param recipient: the calendar user address of the attendee whose data is being processed
        @type recipient: C{str}
        @param remove_matched: whether or not to remove the matching component rather than transfer items
        @type remove_matched: C{bool}

        @return: C{True} if an EXDATE match occurred requiring the incoming component to be removed.
        """

        rid = to_component.getRecurrenceIDUTC()

        transfer_partstat = rid not in needs_action_rids and not reschedule

        # Is there a matching component
        matched = from_calendar.overriddenComponent(rid)
        if matched:
            valarms = [comp for comp in matched.subcomponents() if comp.name() == "VALARM"]
            private_comments = matched.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
            transps = matched.properties("TRANSP")
            completeds = matched.properties("COMPLETED")
            organizer = matched.getProperty("ORGANIZER")
            organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
            attendee = matched.getAttendeeProperty((recipient,))
            attendee_dtstamp = attendee.parameterValue("X-CALENDARSERVER-DTSTAMP") if attendee else None
            other_props = {}
            for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
                props = tuple(matched.properties(pname))
                if props:
                    other_props[pname] = props

            seq_change = Component.compareComponentsForITIP(to_component, matched, use_dtstamp=False) <= 0
            iTipProcessing._transferItems(to_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)

            # Check for incoming DECLINED
            to_attendee = to_component.getAttendeeProperty((recipient,))
            if to_attendee and to_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
                # If existing item has HIDDEN property copy that over
                if matched.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
                    to_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))

            # Remove the old one
            if remove_matched:
                from_calendar.removeComponent(matched)

            # Check to see if the new component is cancelled as that could mean we are copying in the wrong attendee state
            if to_component.propertyValue("STATUS") == "CANCELLED":
                if attendee and to_attendee:
                    to_attendee.setParameter("PARTSTAT", attendee.parameterValue("PARTSTAT", "NEEDS-ACTION"))

        else:
            # Check for incoming DECLINED
            attendee = to_component.getAttendeeProperty((recipient,))
            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
                return True

            master_component = from_calendar.masterComponent()
            seq_change = (Component.compareComponentsForITIP(to_component, master_component, use_dtstamp=False) <= 0) if master_component is not None else True
            iTipProcessing._transferItems(to_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)

        return False