Example #1
0
class ComponentRecur(Component):

    propertyCardinality_STATUS_Fix = (definitions.cICalProperty_STATUS, )

    @staticmethod
    def mapKey(uid, rid=None):
        if uid:
            result = "u:" + uid
            if rid is not None:
                result += rid
            return result
        else:
            return None

    @staticmethod
    def sort_by_dtstart_allday(e1, e2):

        if e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly():
            return e1.self.mStart < e2.self.mStart
        elif e1.self.mStart.isDateOnly():
            return True
        elif (e2.self.mStart.isDateOnly()):
            return False
        elif e1.self.mStart == e2.self.mStart:
            if e1.self.mEnd == e2.self.mEnd:
                # Put ones created earlier in earlier columns in day view
                return e1.self.mStamp < e2.self.mStamp
            else:
                # Put ones that end later in earlier columns in day view
                return e1.self.mEnd > e2.self.mEnd
        else:
            return e1.self.mStart < e2.self.mStart

    @staticmethod
    def sort_by_dtstart(e1, e2):
        if e1.self.mStart == e2.self.mStart:
            if (e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly()
                    or not e1.self.mStart.isDateOnly()
                    and not e2.self.mStart.isDateOnly()):
                return False
            else:
                return e1.self.mStart.isDateOnly()
        else:
            return e1.self.mStart < e2.self.mStart

    def __init__(self, parent=None):
        super(ComponentRecur, self).__init__(parent=parent)
        self.mMaster = self
        self.mMapKey = None
        self.mSummary = None
        self.mStamp = DateTime()
        self.mHasStamp = False
        self.mStart = DateTime()
        self.mHasStart = False
        self.mEnd = DateTime()
        self.mHasEnd = False
        self.mDuration = False
        self.mHasRecurrenceID = False
        self.mAdjustFuture = False
        self.mAdjustPrior = False
        self.mRecurrenceID = None
        self.mRecurrences = None

        # This is a special check we do only for STATUS due to a calendarserver bug
        self.cardinalityChecks += (self.check_cardinality_STATUS_Fix, )

    def duplicate(self, parent=None):
        other = super(ComponentRecur, self).duplicate(parent=parent)

        # Special determination of master
        other.mMaster = self.mMaster if self.recurring() else self

        other.mMapKey = self.mMapKey

        other.mSummary = self.mSummary

        if (self.mStamp is not None):
            other.mStamp = self.mStamp.duplicate()
        other.mHasStamp = self.mHasStamp

        other.mStart = self.mStart.duplicate()
        other.mHasStart = self.mHasStart
        other.mEnd = self.mEnd.duplicate()
        other.mHasEnd = self.mHasEnd
        other.mDuration = self.mDuration

        other.mHasRecurrenceID = self.mHasRecurrenceID
        other.mAdjustFuture = self.mAdjustFuture
        other.mAdjustPrior = self.mAdjustPrior
        if self.mRecurrenceID is not None:
            other.mRecurrenceID = self.mRecurrenceID.duplicate()

        other._resetRecurrenceSet()

        return other

    def canGenerateInstance(self):
        return not self.mHasRecurrenceID

    def recurring(self):
        return (self.mMaster is not None) and (self.mMaster is not self)

    def setMaster(self, master):
        self.mMaster = master
        self.initFromMaster()

    def getMaster(self):
        return self.mMaster

    def getMapKey(self):

        if self.mMapKey is None:
            self.mMapKey = str(uuid.uuid4())
        return self.mMapKey

    def getMasterKey(self):
        return ComponentRecur.mapKey(self.mUID)

    def initDTSTAMP(self):
        # Save new one
        super(ComponentRecur, self).initDTSTAMP()

        # Get the new one
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP)
        self.mHasStamp = temp is not None
        if self.mHasStamp:
            self.mStamp = temp

    def getStamp(self):
        return self.mStamp

    def hasStamp(self):
        return self.mHasStamp

    def getStart(self):
        return self.mStart

    def hasStart(self):
        return self.mHasStart

    def getEnd(self):
        return self.mEnd

    def hasEnd(self):
        return self.mHasEnd

    def useDuration(self):
        return self.mDuration

    def isRecurrenceInstance(self):
        return self.mHasRecurrenceID

    def isAdjustFuture(self):
        return self.mAdjustFuture

    def isAdjustPrior(self):
        return self.mAdjustPrior

    def getRecurrenceID(self):
        return self.mRecurrenceID

    def isRecurring(self):
        return (self.mRecurrences
                is not None) and self.mRecurrences.hasRecurrence()

    def getRecurrenceSet(self):
        return self.mRecurrences

    def setUID(self, uid):
        super(ComponentRecur, self).setUID(uid)

        # Update the map key
        if self.mHasRecurrenceID:
            self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText())
        else:
            self.mMapKey = self.mapKey(self.mUID)

    def getSummary(self):
        return self.mSummary

    def setSummary(self, summary):
        self.mSummary = summary

    def getDescription(self):
        # Get DESCRIPTION
        txt = self.loadValueString(definitions.cICalProperty_DESCRIPTION)
        if txt is not None:
            return txt
        else:
            return ""

    def getLocation(self):
        # Get LOCATION
        txt = self.loadValueString(definitions.cICalProperty_LOCATION)
        if txt is not None:
            return txt
        else:
            return ""

    def finalise(self):
        super(ComponentRecur, self).finalise()

        # Get DTSTAMP
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP)
        self.mHasStamp = temp is not None
        if self.mHasStamp:
            self.mStamp = temp

        # Get DTSTART
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART)
        self.mHasStart = temp is not None
        if self.mHasStart:
            self.mStart = temp

        # Get DTEND
        temp = self.loadValueDateTime(definitions.cICalProperty_DTEND)
        if temp is None:
            # Try DURATION instead
            temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
            if temp is not None:
                self.mHasEnd = False
                self.mEnd = self.mStart + temp
                self.mDuration = True
            else:
                # If no end or duration then use the start (bumped by one day for
                # an all day event)
                self.mHasEnd = False
                self.mEnd = self.mStart.duplicate()
                if self.mEnd.isDateOnly():
                    self.mEnd.offsetDay(1)
                self.mDuration = False
        else:
            self.mHasEnd = True
            self.mEnd = temp
            self.mDuration = False

        # Get SUMMARY
        temp = self.loadValueString(definitions.cICalProperty_SUMMARY)
        if temp is not None:
            self.mSummary = temp

        # Get RECURRENCE-ID
        self.mHasRecurrenceID = (self.countProperty(
            definitions.cICalProperty_RECURRENCE_ID) != 0)
        if self.mHasRecurrenceID:
            self.mRecurrenceID = self.loadValueDateTime(
                definitions.cICalProperty_RECURRENCE_ID)

        # Update the map key
        if self.mHasRecurrenceID:
            self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText())

            # Also get the RANGE parameter
            attrs = self.findFirstProperty(
                definitions.cICalProperty_RECURRENCE_ID).getParameters()
            if definitions.cICalParameter_RANGE in attrs:
                self.mAdjustFuture = (
                    attrs[definitions.cICalParameter_RANGE][0].getFirstValue()
                    == definitions.cICalParameter_RANGE_THISANDFUTURE)
                self.mAdjustPrior = (
                    attrs[definitions.cICalParameter_RANGE][0].getFirstValue()
                    == definitions.cICalParameter_RANGE_THISANDPRIOR)
            else:
                self.mAdjustFuture = False
                self.mAdjustPrior = False
        else:
            self.mMapKey = self.mapKey(self.mUID)

        self._resetRecurrenceSet()

    def validate(self, doFix=False):
        """
        Validate the data in this component and optionally fix any problems. Return
        a tuple containing two lists: the first describes problems that were fixed, the
        second problems that were not fixed. Caller can then decide what to do with unfixed
        issues.
        """

        # Do normal checks
        fixed, unfixed = super(ComponentRecur, self).validate(doFix)

        # Check that any UNTIL value matches that for DTSTART
        if self.mHasStart and self.mRecurrences:
            dtutc = self.mStart.duplicateAsUTC()
            for rrule in self.mRecurrences.getRules():
                if rrule.getUseUntil():
                    if rrule.getUntil().isDateOnly() ^ self.mStart.isDateOnly(
                    ):
                        logProblem = "[%s] Value types must match: %s, %s" % (
                            self.getType(),
                            definitions.cICalProperty_DTSTART,
                            definitions.cICalValue_RECUR_UNTIL,
                        )
                        if doFix:
                            rrule.getUntil().setDateOnly(
                                self.mStart.isDateOnly())
                            if not self.mStart.isDateOnly():
                                rrule.getUntil().setHHMMSS(
                                    dtutc.getHours(), dtutc.getMinutes(),
                                    dtutc.getSeconds())
                                rrule.getUntil().setTimezone(
                                    Timezone(utc=True))
                            self.mRecurrences.changed()
                            fixed.append(logProblem)
                        else:
                            unfixed.append(logProblem)

        return fixed, unfixed

    def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix):
        """
        Special for bug with STATUS where STATUS:CANCELLED is added alongside
        another STATUS. In this case we want STATUS:CANCELLED to win.
        """
        for propname in self.propertyCardinality_STATUS_Fix:
            if self.countProperty(propname) > 1:
                logProblem = "[%s] Too many properties: %s" % (self.getType(),
                                                               propname)
                if doFix:
                    # Check that one of them is STATUS:CANCELLED
                    for prop in self.getProperties(propname):
                        if prop.getTextValue().getValue().upper(
                        ) == definitions.cICalProperty_STATUS_CANCELLED:
                            self.removeProperties(propname)
                            self.addProperty(
                                Property(
                                    propname, definitions.
                                    cICalProperty_STATUS_CANCELLED))
                            fixed.append(logProblem)
                            break
                    else:
                        unfixed.append(logProblem)
                else:
                    unfixed.append(logProblem)

    def _resetRecurrenceSet(self):
        # May need to create items
        self.mRecurrences = None
        if ((self.countProperty(definitions.cICalProperty_RRULE) != 0)
                or (self.countProperty(definitions.cICalProperty_RDATE) != 0)
                or (self.countProperty(definitions.cICalProperty_EXRULE) != 0)
                or
            (self.countProperty(definitions.cICalProperty_EXDATE) != 0)):

            self.mRecurrences = RecurrenceSet()

            # Get RRULEs
            self.loadValueRRULE(definitions.cICalProperty_RRULE,
                                self.mRecurrences, True)

            # Get RDATEs
            self.loadValueRDATE(definitions.cICalProperty_RDATE,
                                self.mRecurrences, True)

            # Get EXRULEs
            self.loadValueRRULE(definitions.cICalProperty_EXRULE,
                                self.mRecurrences, False)

            # Get EXDATEs
            self.loadValueRDATE(definitions.cICalProperty_EXDATE,
                                self.mRecurrences, False)

    def FixStartEnd(self):
        # End is always greater than start if start exists
        if self.mHasStart and self.mEnd <= self.mStart:
            # Use the start
            self.mEnd = self.mStart.duplicate()
            self.mDuration = False

            # Adjust to approriate non-inclusive end point
            if self.mStart.isDateOnly():
                self.mEnd.offsetDay(1)

                # For all day events it makes sense to use duration
                self.mDuration = True
            else:
                # Use end of current day
                self.mEnd.offsetDay(1)
                self.mEnd.setHHMMSS(0, 0, 0)

    def expandPeriod(self, period, results):
        # Check for recurrence and True master
        if ((self.mRecurrences is not None)
                and self.mRecurrences.hasRecurrence()
                and not self.isRecurrenceInstance()):
            # Expand recurrences within the range
            items = []
            self.mRecurrences.expand(self.mStart, period, items)

            # Look for overridden recurrence items
            cal = self.mParentComponent
            if cal is not None:
                # Remove recurrence instances from the list of items
                recurs = []
                cal.getRecurrenceInstancesIds(
                    definitions.cICalComponent_VEVENT, self.getUID(), recurs)
                recurs.sort()
                if len(recurs) != 0:
                    temp = []
                    temp = set_difference(items, recurs)
                    items = temp

                    # Now get actual instances
                    instances = []
                    cal.getRecurrenceInstancesItems(
                        definitions.cICalComponent_VEVENT, self.getUID(),
                        instances)

                    # Get list of each ones with RANGE
                    prior = []
                    future = []
                    for iter in instances:
                        if iter.isAdjustPrior():
                            prior.append(iter)
                        if iter.isAdjustFuture():
                            future.append(iter)

                    # Check for special behaviour
                    if len(prior) + len(future) == 0:
                        # Add each expanded item
                        for iter in items:
                            results.append(self.createExpanded(self, iter))
                    else:
                        # Sort each list first
                        prior.sort(self.sort_by_dtstart)
                        future.sort(self.sort_by_dtstart)

                        # Add each expanded item
                        for iter1 in items:

                            # Now step through each using the slave item
                            # instead of the master as appropriate
                            slave = None

                            # Find most appropriate THISANDPRIOR item
                            for i in range(len(prior) - 1, 0, -1):
                                riter2 = prior[i]
                                if riter2.getStart() > iter1:
                                    slave = riter2
                                    break

                            # Find most appropriate THISANDFUTURE item
                            for i in range(len(future) - 1, 0, -1):
                                riter2 = future.elementAt(i)
                                if riter2.getStart() < iter1:
                                    slave = riter2
                                    break

                            if slave is None:
                                slave = self
                            results.append(self.createExpanded(slave, iter1))
                else:
                    # Add each expanded item
                    for iter in items:
                        results.append(self.createExpanded(self, iter))

        elif self.withinPeriod(period):
            if self.isRecurrenceInstance():
                rid = self.mRecurrenceID
            else:
                rid = None
            results.append(ComponentExpanded(self, rid))

    def withinPeriod(self, period):
        # Check for recurrence
        if ((self.mRecurrences is not None)
                and self.mRecurrences.hasRecurrence()):
            items = []
            self.mRecurrences.expand(self.mStart, period, items)
            return len(items) != 0
        else:
            # Does event span the period (assume self.mEnd > self.mStart)
            # Check start (inclusive) and end (exclusive)
            if self.mEnd <= period.getStart() or self.mStart >= period.getEnd(
            ):
                return False
            else:
                return True

    def changedRecurrence(self):
        # Clear cached values
        if self.mRecurrences is not None:
            self.mRecurrences.changed()

    # Editing
    def editSummary(self, summary):
        # Updated cached value
        self.mSummary = summary

        # Remove existing items
        self.editProperty(definitions.cICalProperty_SUMMARY, summary)

    def editDetails(self, description, location):

        # Edit existing items
        self.editProperty(definitions.cICalProperty_DESCRIPTION, description)
        self.editProperty(definitions.cICalProperty_LOCATION, location)

    def editTiming(self):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = False
        self.mDuration = False
        self.mStart.setToday()
        self.mEnd.setToday()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

    def editTimingDue(self, due):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = True
        self.mDuration = False
        self.mStart = due
        self.mEnd = due

        # Remove existing DUE & DTSTART & DTEND & DURATION items
        self.removeProperties(definitions.cICalProperty_DUE)
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)

        # Now create properties
        prop = Property(definitions.cICalProperty_DUE, due)
        self.addProperty(prop)

    def editTimingStartEnd(self, start, end):
        # Updated cached values
        self.mHasStart = self.mHasEnd = True
        self.mStart = start
        self.mEnd = end
        self.mDuration = False
        self.FixStartEnd()
        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the end one day after the start, ignore it
        temp = start.duplicate()
        temp.offsetDay(1)
        if not start.isDateOnly() or end != temp:
            prop = Property(definitions.cICalProperty_DTEND, end)
            self.addProperty(prop)

    def editTimingStartDuration(self, start, duration):
        # Updated cached values
        self.mHasStart = True
        self.mHasEnd = False
        self.mStart = start
        self.mEnd = start + duration
        self.mDuration = True

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the duration is one day, ignore it
        if (not start.isDateOnly() or (duration.getWeeks() != 0)
                or (duration.getDays() > 1)):
            prop = Property(definitions.cICalProperty_DURATION, duration)
            self.addProperty(prop)

    def editRecurrenceSet(self, recurs):
        # Must have items
        if self.mRecurrences is None:
            self.mRecurrences = RecurrenceSet()

        # Updated cached values
        self.mRecurrences = recurs

        # Remove existing RRULE, EXRULE, RDATE & EXDATE
        self.removeProperties(definitions.cICalProperty_RRULE)
        self.removeProperties(definitions.cICalProperty_EXRULE)
        self.removeProperties(definitions.cICalProperty_RDATE)
        self.removeProperties(definitions.cICalProperty_EXDATE)

        # Now create properties
        for iter in self.mRecurrences.getRules():
            prop = Property(definitions.cICalProperty_RRULE, iter)
            self.addProperty(prop)
        for iter in self.getExrules():
            prop = Property(definitions.cICalProperty_EXRULE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getDates():
            prop = Property(definitions.cICalProperty_RDATE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getExdates():
            prop = Property(definitions.cICalProperty_EXDATE, iter)
            self.addProperty(prop)

    def excludeRecurrence(self, start):
        # Must have items
        if self.mRecurrences is None:
            return

        # Add to recurrence set and clear cache
        self.mRecurrences.subtract(start)

        # Add property
        prop = Property(definitions.cICalProperty_EXDATE, start)
        self.addProperty(prop)

    def excludeFutureRecurrence(self, start):
        # Must have items
        if self.mRecurrences is None:
            return

        # Adjust RRULES to end before start
        self.mRecurrences.excludeFutureRecurrence(start)

        # Remove existing RRULE & RDATE
        self.removeProperties(definitions.cICalProperty_RRULE)
        self.removeProperties(definitions.cICalProperty_RDATE)

        # Now create properties
        for iter in self.mRecurrences.getRules():
            prop = Property(definitions.cICalProperty_RRULE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getDates():
            prop = Property(definitions.cICalProperty_RDATE, iter)
            self.addProperty(prop)

    def initFromMaster(self):
        # Only if not master
        if self.recurring():
            # Redo this to get cached values from master
            self.finalise()

            # If this component does not have its own start property, use the
            # recurrence id
            # i.e. the start time of this instance has not changed - something
            # else has
            if not self.hasProperty(definitions.cICalProperty_DTSTART):
                self.mStart = self.mRecurrenceID

            # If this component does not have its own end/duration property,
            # the determine
            # the end from the master duration
            if (not self.hasProperty(definitions.cICalProperty_DTEND) and
                    not self.hasProperty(definitions.cICalProperty_DURATION)):
                # End is based on original events settings
                self.mEnd = self.mStart + (self.mMaster.getEnd() -
                                           self.mMaster.getStart())

            # If this instance has a duration, but no start of its own, then we
            # need to readjust the end
            # to account for the start being changed to the recurrence id
            elif (self.hasProperty(definitions.cICalProperty_DURATION)
                  and not self.hasProperty(definitions.cICalProperty_DTSTART)):
                temp = self.loadValueDuration(
                    definitions.cICalProperty_DURATION)
                self.mEnd = self.mStart + temp

    def createExpanded(self, master, recurid):
        return ComponentExpanded(master, recurid)
Example #2
0
class ComponentRecur(Component):

    propertyCardinality_STATUS_Fix = (
        definitions.cICalProperty_STATUS,
    )

    @staticmethod
    def mapKey(uid, rid=None):
        if uid:
            result = "u:" + uid
            if rid is not None:
                result += rid
            return result
        else:
            return None


    @staticmethod
    def sort_by_dtstart_allday(e1, e2):

        if e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly():
            return e1.self.mStart < e2.self.mStart
        elif e1.self.mStart.isDateOnly():
            return True
        elif (e2.self.mStart.isDateOnly()):
            return False
        elif e1.self.mStart == e2.self.mStart:
            if e1.self.mEnd == e2.self.mEnd:
                # Put ones created earlier in earlier columns in day view
                return e1.self.mStamp < e2.self.mStamp
            else:
                # Put ones that end later in earlier columns in day view
                return e1.self.mEnd > e2.self.mEnd
        else:
            return e1.self.mStart < e2.self.mStart


    @staticmethod
    def sort_by_dtstart(e1, e2):
        if e1.self.mStart == e2.self.mStart:
            if (
                e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly() or
                not e1.self.mStart.isDateOnly() and not e2.self.mStart.isDateOnly()
            ):
                return False
            else:
                return e1.self.mStart.isDateOnly()
        else:
            return e1.self.mStart < e2.self.mStart


    def __init__(self, parent=None):
        super(ComponentRecur, self).__init__(parent=parent)
        self.mMaster = self
        self.mMapKey = None
        self.mSummary = None
        self.mStamp = DateTime()
        self.mHasStamp = False
        self.mStart = DateTime()
        self.mHasStart = False
        self.mEnd = DateTime()
        self.mHasEnd = False
        self.mDuration = False
        self.mHasRecurrenceID = False
        self.mAdjustFuture = False
        self.mAdjustPrior = False
        self.mRecurrenceID = None
        self.mRecurrences = None

        # This is a special check we do only for STATUS due to a calendarserver bug
        self.cardinalityChecks += (
            self.check_cardinality_STATUS_Fix,
        )


    def duplicate(self, parent=None):
        other = super(ComponentRecur, self).duplicate(parent=parent)

        # Special determination of master
        other.mMaster = self.mMaster if self.recurring() else self

        other.mMapKey = self.mMapKey

        other.mSummary = self.mSummary

        if (self.mStamp is not None):
            other.mStamp = self.mStamp.duplicate()
        other.mHasStamp = self.mHasStamp

        other.mStart = self.mStart.duplicate()
        other.mHasStart = self.mHasStart
        other.mEnd = self.mEnd.duplicate()
        other.mHasEnd = self.mHasEnd
        other.mDuration = self.mDuration

        other.mHasRecurrenceID = self.mHasRecurrenceID
        other.mAdjustFuture = self.mAdjustFuture
        other.mAdjustPrior = self.mAdjustPrior
        if self.mRecurrenceID is not None:
            other.mRecurrenceID = self.mRecurrenceID.duplicate()

        other._resetRecurrenceSet()

        return other


    def canGenerateInstance(self):
        return not self.mHasRecurrenceID


    def recurring(self):
        return (self.mMaster is not None) and (self.mMaster is not self)


    def setMaster(self, master):
        self.mMaster = master
        self.initFromMaster()


    def getMaster(self):
        return self.mMaster


    def getMapKey(self):

        if self.mMapKey is None:
            self.mMapKey = str(uuid.uuid4())
        return self.mMapKey


    def getMasterKey(self):
        return ComponentRecur.mapKey(self.mUID)


    def initDTSTAMP(self):
        # Save new one
        super(ComponentRecur, self).initDTSTAMP()

        # Get the new one
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP)
        self.mHasStamp = temp is not None
        if self.mHasStamp:
            self.mStamp = temp


    def getStamp(self):
        return self.mStamp


    def hasStamp(self):
        return self.mHasStamp


    def getStart(self):
        return self.mStart


    def hasStart(self):
        return self.mHasStart


    def getEnd(self):
        return self.mEnd


    def hasEnd(self):
        return self.mHasEnd


    def useDuration(self):
        return self.mDuration


    def isRecurrenceInstance(self):
        return self.mHasRecurrenceID


    def isAdjustFuture(self):
        return self.mAdjustFuture


    def isAdjustPrior(self):
        return self.mAdjustPrior


    def getRecurrenceID(self):
        return self.mRecurrenceID


    def isRecurring(self):
        return (self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()


    def getRecurrenceSet(self):
        return self.mRecurrences


    def setUID(self, uid):
        super(ComponentRecur, self).setUID(uid)

        # Update the map key
        if self.mHasRecurrenceID:
            self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText())
        else:
            self.mMapKey = self.mapKey(self.mUID)


    def getSummary(self):
        return self.mSummary


    def setSummary(self, summary):
        self.mSummary = summary


    def getDescription(self):
        # Get DESCRIPTION
        txt = self.loadValueString(definitions.cICalProperty_DESCRIPTION)
        if txt is not None:
            return txt
        else:
            return ""


    def getLocation(self):
        # Get LOCATION
        txt = self.loadValueString(definitions.cICalProperty_LOCATION)
        if txt is not None:
            return txt
        else:
            return ""


    def finalise(self):
        super(ComponentRecur, self).finalise()

        # Get DTSTAMP
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP)
        self.mHasStamp = temp is not None
        if self.mHasStamp:
            self.mStamp = temp

        # Get DTSTART
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART)
        self.mHasStart = temp is not None
        if self.mHasStart:
            self.mStart = temp

        # Get DTEND
        temp = self.loadValueDateTime(definitions.cICalProperty_DTEND)
        if temp is None:
            # Try DURATION instead
            temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
            if temp is not None:
                self.mHasEnd = False
                self.mEnd = self.mStart + temp
                self.mDuration = True
            else:
                # If no end or duration then use the start (bumped by one day for
                # an all day event)
                self.mHasEnd = False
                self.mEnd = self.mStart.duplicate()
                if self.mEnd.isDateOnly():
                    self.mEnd.offsetDay(1)
                self.mDuration = False
        else:
            self.mHasEnd = True
            self.mEnd = temp
            self.mDuration = False

        # Get SUMMARY
        temp = self.loadValueString(definitions.cICalProperty_SUMMARY)
        if temp is not None:
            self.mSummary = temp

        # Get RECURRENCE-ID
        self.mHasRecurrenceID = (self.countProperty(definitions.cICalProperty_RECURRENCE_ID) != 0)
        if self.mHasRecurrenceID:
            self.mRecurrenceID = self.loadValueDateTime(definitions.cICalProperty_RECURRENCE_ID)

        # Update the map key
        if self.mHasRecurrenceID:
            self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText())

            # Also get the RANGE parameter
            attrs = self.findFirstProperty(definitions.cICalProperty_RECURRENCE_ID).getParameters()
            if definitions.cICalParameter_RANGE in attrs:
                self.mAdjustFuture = (attrs[definitions.cICalParameter_RANGE][0].getFirstValue() == definitions.cICalParameter_RANGE_THISANDFUTURE)
                self.mAdjustPrior = (attrs[definitions.cICalParameter_RANGE][0].getFirstValue() == definitions.cICalParameter_RANGE_THISANDPRIOR)
            else:
                self.mAdjustFuture = False
                self.mAdjustPrior = False
        else:
            self.mMapKey = self.mapKey(self.mUID)

        self._resetRecurrenceSet()


    def validate(self, doFix=False):
        """
        Validate the data in this component and optionally fix any problems. Return
        a tuple containing two lists: the first describes problems that were fixed, the
        second problems that were not fixed. Caller can then decide what to do with unfixed
        issues.
        """

        # Do normal checks
        fixed, unfixed = super(ComponentRecur, self).validate(doFix)

        # Check that any UNTIL value matches that for DTSTART
        if self.mHasStart and self.mRecurrences:
            dtutc = self.mStart.duplicateAsUTC()
            for rrule in self.mRecurrences.getRules():
                if rrule.getUseUntil():
                    if rrule.getUntil().isDateOnly() ^ self.mStart.isDateOnly():
                        logProblem = "[%s] Value types must match: %s, %s" % (
                            self.getType(),
                            definitions.cICalProperty_DTSTART,
                            definitions.cICalValue_RECUR_UNTIL,
                        )
                        if doFix:
                            rrule.getUntil().setDateOnly(self.mStart.isDateOnly())
                            if not self.mStart.isDateOnly():
                                rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds())
                                rrule.getUntil().setTimezone(Timezone(utc=True))
                            self.mRecurrences.changed()
                            fixed.append(logProblem)
                        else:
                            unfixed.append(logProblem)

        return fixed, unfixed


    def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix):
        """
        Special for bug with STATUS where STATUS:CANCELLED is added alongside
        another STATUS. In this case we want STATUS:CANCELLED to win.
        """
        for propname in self.propertyCardinality_STATUS_Fix:
            if self.countProperty(propname) > 1:
                logProblem = "[%s] Too many properties: %s" % (self.getType(), propname)
                if doFix:
                    # Check that one of them is STATUS:CANCELLED
                    for prop in self.getProperties(propname):
                        if prop.getTextValue().getValue().upper() == definitions.cICalProperty_STATUS_CANCELLED:
                            self.removeProperties(propname)
                            self.addProperty(Property(propname, definitions.cICalProperty_STATUS_CANCELLED))
                            fixed.append(logProblem)
                            break
                    else:
                        unfixed.append(logProblem)
                else:
                    unfixed.append(logProblem)


    def _resetRecurrenceSet(self):
        # May need to create items
        self.mRecurrences = None
        if (
            (self.countProperty(definitions.cICalProperty_RRULE) != 0) or
            (self.countProperty(definitions.cICalProperty_RDATE) != 0) or
            (self.countProperty(definitions.cICalProperty_EXRULE) != 0) or
            (self.countProperty(definitions.cICalProperty_EXDATE) != 0)
        ):

            self.mRecurrences = RecurrenceSet()

            # Get RRULEs
            self.loadValueRRULE(definitions.cICalProperty_RRULE, self.mRecurrences, True)

            # Get RDATEs
            self.loadValueRDATE(definitions.cICalProperty_RDATE, self.mRecurrences, True)

            # Get EXRULEs
            self.loadValueRRULE(definitions.cICalProperty_EXRULE, self.mRecurrences, False)

            # Get EXDATEs
            self.loadValueRDATE(definitions.cICalProperty_EXDATE, self.mRecurrences, False)


    def FixStartEnd(self):
        # End is always greater than start if start exists
        if self.mHasStart and self.mEnd <= self.mStart:
            # Use the start
            self.mEnd = self.mStart.duplicate()
            self.mDuration = False

            # Adjust to approriate non-inclusive end point
            if self.mStart.isDateOnly():
                self.mEnd.offsetDay(1)

                # For all day events it makes sense to use duration
                self.mDuration = True
            else:
                # Use end of current day
                self.mEnd.offsetDay(1)
                self.mEnd.setHHMMSS(0, 0, 0)


    def expandPeriod(self, period, results):
        # Check for recurrence and True master
        if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()
                and not self.isRecurrenceInstance()):
            # Expand recurrences within the range
            items = []
            self.mRecurrences.expand(self.mStart, period, items)

            # Look for overridden recurrence items
            cal = self.mParentComponent
            if cal is not None:
                # Remove recurrence instances from the list of items
                recurs = []
                cal.getRecurrenceInstancesIds(definitions.cICalComponent_VEVENT, self.getUID(), recurs)
                recurs.sort()
                if len(recurs) != 0:
                    temp = []
                    temp = set_difference(items, recurs)
                    items = temp

                    # Now get actual instances
                    instances = []
                    cal.getRecurrenceInstancesItems(definitions.cICalComponent_VEVENT, self.getUID(), instances)

                    # Get list of each ones with RANGE
                    prior = []
                    future = []
                    for iter in instances:
                        if iter.isAdjustPrior():
                            prior.append(iter)
                        if iter.isAdjustFuture():
                            future.append(iter)

                    # Check for special behaviour
                    if len(prior) + len(future) == 0:
                        # Add each expanded item
                        for iter in items:
                            results.append(self.createExpanded(self, iter))
                    else:
                        # Sort each list first
                        prior.sort(self.sort_by_dtstart)
                        future.sort(self.sort_by_dtstart)

                        # Add each expanded item
                        for iter1 in items:

                            # Now step through each using the slave item
                            # instead of the master as appropriate
                            slave = None

                            # Find most appropriate THISANDPRIOR item
                            for i in range(len(prior) - 1, 0, -1):
                                riter2 = prior[i]
                                if riter2.getStart() > iter1:
                                    slave = riter2
                                    break

                            # Find most appropriate THISANDFUTURE item
                            for i in range(len(future) - 1, 0, -1):
                                riter2 = future.elementAt(i)
                                if riter2.getStart() < iter1:
                                    slave = riter2
                                    break

                            if slave is None:
                                slave = self
                            results.append(self.createExpanded(slave, iter1))
                else:
                    # Add each expanded item
                    for iter in items:
                        results.append(self.createExpanded(self, iter))

        elif self.withinPeriod(period):
            if self.isRecurrenceInstance():
                rid = self.mRecurrenceID
            else:
                rid = None
            results.append(ComponentExpanded(self, rid))


    def withinPeriod(self, period):
        # Check for recurrence
        if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()):
            items = []
            self.mRecurrences.expand(self.mStart, period, items)
            return len(items) != 0
        else:
            # Does event span the period (assume self.mEnd > self.mStart)
            # Check start (inclusive) and end (exclusive)
            if self.mEnd <= period.getStart() or self.mStart >= period.getEnd():
                return False
            else:
                return True


    def changedRecurrence(self):
        # Clear cached values
        if self.mRecurrences is not None:
            self.mRecurrences.changed()


    # Editing
    def editSummary(self, summary):
        # Updated cached value
        self.mSummary = summary

        # Remove existing items
        self.editProperty(definitions.cICalProperty_SUMMARY, summary)


    def editDetails(self, description, location):

        # Edit existing items
        self.editProperty(definitions.cICalProperty_DESCRIPTION, description)
        self.editProperty(definitions.cICalProperty_LOCATION, location)


    def editTiming(self):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = False
        self.mDuration = False
        self.mStart.setToday()
        self.mEnd.setToday()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)


    def editTimingDue(self, due):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = True
        self.mDuration = False
        self.mStart = due
        self.mEnd = due

        # Remove existing DUE & DTSTART & DTEND & DURATION items
        self.removeProperties(definitions.cICalProperty_DUE)
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)

        # Now create properties
        prop = Property(definitions.cICalProperty_DUE, due)
        self.addProperty(prop)


    def editTimingStartEnd(self, start, end):
        # Updated cached values
        self.mHasStart = self.mHasEnd = True
        self.mStart = start
        self.mEnd = end
        self.mDuration = False
        self.FixStartEnd()
        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the end one day after the start, ignore it
        temp = start.duplicate()
        temp.offsetDay(1)
        if not start.isDateOnly() or end != temp:
            prop = Property(definitions.cICalProperty_DTEND, end)
            self.addProperty(prop)


    def editTimingStartDuration(self, start, duration):
        # Updated cached values
        self.mHasStart = True
        self.mHasEnd = False
        self.mStart = start
        self.mEnd = start + duration
        self.mDuration = True

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the duration is one day, ignore it
        if (not start.isDateOnly() or (duration.getWeeks() != 0)
                or (duration.getDays() > 1)):
            prop = Property(definitions.cICalProperty_DURATION, duration)
            self.addProperty(prop)


    def editRecurrenceSet(self, recurs):
        # Must have items
        if self.mRecurrences is None:
            self.mRecurrences = RecurrenceSet()

        # Updated cached values
        self.mRecurrences = recurs

        # Remove existing RRULE, EXRULE, RDATE & EXDATE
        self.removeProperties(definitions.cICalProperty_RRULE)
        self.removeProperties(definitions.cICalProperty_EXRULE)
        self.removeProperties(definitions.cICalProperty_RDATE)
        self.removeProperties(definitions.cICalProperty_EXDATE)

        # Now create properties
        for iter in self.mRecurrences.getRules():
            prop = Property(definitions.cICalProperty_RRULE, iter)
            self.addProperty(prop)
        for iter in self.getExrules():
            prop = Property(definitions.cICalProperty_EXRULE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getDates():
            prop = Property(definitions.cICalProperty_RDATE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getExdates():
            prop = Property(definitions.cICalProperty_EXDATE, iter)
            self.addProperty(prop)


    def excludeRecurrence(self, start):
        # Must have items
        if self.mRecurrences is None:
            return

        # Add to recurrence set and clear cache
        self.mRecurrences.subtract(start)

        # Add property
        prop = Property(definitions.cICalProperty_EXDATE, start)
        self.addProperty(prop)


    def excludeFutureRecurrence(self, start):
        # Must have items
        if self.mRecurrences is None:
            return

        # Adjust RRULES to end before start
        self.mRecurrences.excludeFutureRecurrence(start)

        # Remove existing RRULE & RDATE
        self.removeProperties(definitions.cICalProperty_RRULE)
        self.removeProperties(definitions.cICalProperty_RDATE)

        # Now create properties
        for iter in self.mRecurrences.getRules():
            prop = Property(definitions.cICalProperty_RRULE, iter)
            self.addProperty(prop)
        for iter in self.mRecurrences.getDates():
            prop = Property(definitions.cICalProperty_RDATE, iter)
            self.addProperty(prop)


    def initFromMaster(self):
        # Only if not master
        if self.recurring():
            # Redo this to get cached values from master
            self.finalise()

            # If this component does not have its own start property, use the
            # recurrence id
            # i.e. the start time of this instance has not changed - something
            # else has
            if not self.hasProperty(definitions.cICalProperty_DTSTART):
                self.mStart = self.mRecurrenceID

            # If this component does not have its own end/duration property,
            # the determine
            # the end from the master duration
            if (
                not self.hasProperty(definitions.cICalProperty_DTEND) and
                not self.hasProperty(definitions.cICalProperty_DURATION)
            ):
                # End is based on original events settings
                self.mEnd = self.mStart + (self.mMaster.getEnd() - self.mMaster.getStart())

            # If this instance has a duration, but no start of its own, then we
            # need to readjust the end
            # to account for the start being changed to the recurrence id
            elif (self.hasProperty(definitions.cICalProperty_DURATION) and
                  not self.hasProperty(definitions.cICalProperty_DTSTART)):
                temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
                self.mEnd = self.mStart + temp


    def createExpanded(self, master, recurid):
        return ComponentExpanded(master, recurid)
Example #3
0
class VFreeBusy(Component):

    propertyCardinality_1 = (
        definitions.cICalProperty_DTSTAMP,
        definitions.cICalProperty_UID,
    )

    propertyCardinality_0_1 = (
        definitions.cICalProperty_CONTACT,
        definitions.cICalProperty_DTSTART,
        definitions.cICalProperty_DTEND,
        definitions.cICalProperty_ORGANIZER,
        definitions.cICalProperty_URL,
    )

    propertyValueChecks = ICALENDAR_VALUE_CHECKS

    def __init__(self, parent=None):
        super(VFreeBusy, self).__init__(parent=parent)
        self.mStart = DateTime()
        self.mHasStart = False
        self.mEnd = DateTime()
        self.mHasEnd = False
        self.mDuration = False
        self.mCachedBusyTime = False
        self.mSpanPeriod = None
        self.mBusyTime = None

    def duplicate(self, parent=None):
        other = super(VFreeBusy, self).duplicate(parent=parent)
        other.mStart = self.mStart.duplicate()
        other.mHasStart = self.mHasStart
        other.mEnd = self.mEnd.duplicate()
        other.mHasEnd = self.mHasEnd
        other.mDuration = self.mDuration
        other.mCachedBusyTime = False
        other.mBusyTime = None
        return other

    def getType(self):
        return definitions.cICalComponent_VFREEBUSY

    def getMimeComponentName(self):
        return itipdefinitions.cICalMIMEComponent_VFREEBUSY

    def finalise(self):
        # Do inherited
        super(VFreeBusy, self).finalise()

        # Get DTSTART
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART)
        self.mHasStart = temp is not None
        if self.mHasStart:
            self.mStart = temp

        # Get DTEND
        temp = self.loadValueDateTime(definitions.cICalProperty_DTEND)
        if temp is None:
            # Try DURATION instead
            temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
            if temp is not None:
                self.mEnd = self.mStart + temp
                self.mDuration = True
            else:
                # Force end to start, which will then be fixed to sensible
                # value later
                self.mEnd = self.mStart
        else:
            self.mHasEnd = True
            self.mDuration = False
            self.mEnd = temp

    def fixStartEnd(self):
        # End is always greater than start if start exists
        if self.mHasStart and self.mEnd <= self.mStart:
            # Use the start
            self.mEnd = self.mStart.duplicate()
            self.mDuration = False

            # Adjust to appropiate non-inclusive end point
            if self.mStart.isDateOnly():
                self.mEnd.offsetDay(1)

                # For all day events it makes sense to use duration
                self.mDuration = True
            else:
                # Use end of current day
                self.mEnd.offsetDay(1)
                self.mEnd.setHHMMSS(0, 0, 0)

    def getStart(self):
        return self.mStart

    def hasStart(self):
        return self.mHasStart

    def getEnd(self):
        return self.mEnd

    def hasEnd(self):
        return self.mHasEnd

    def useDuration(self):
        return self.mDuration

    def getSpanPeriod(self):
        return self.mSpanPeriod

    def getBusyTime(self):
        return self.mBusyTime

    def editTiming(self):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = False
        self.mDuration = False
        self.mStart.setToday()
        self.mEnd.setToday()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)

    def editTimingStartEnd(self, start, end):
        # Updated cached values
        self.mHasStart = self.mHasEnd = True
        self.mStart = start
        self.mEnd = end
        self.mDuration = False
        self.fixStartEnd()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the end one day after the start, ignore it
        temp = start.duplicate()
        temp.offsetDay(1)
        if not start.isDateOnly() or end != temp:
            prop = Property(definitions.cICalProperty_DTEND, end)
            self.addProperty(prop)

    def editTimingStartDuration(self, start, duration):
        # Updated cached values
        self.mHasStart = True
        self.mHasEnd = False
        self.mStart = start
        self.mEnd = start + duration
        self.mDuration = True

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the duration is one day, ignore it
        if (not start.isDateOnly() or (duration.getWeeks() != 0)
                or (duration.getDays() > 1)):
            prop = Property(definitions.cICalProperty_DURATION, duration)
            self.addProperty(prop)

    # Generating info
    def expandPeriodComp(self, period, list):
        # Cache the busy-time details if not done already
        if not self.mCachedBusyTime:
            self.cacheBusyTime()

        # See if period intersects the busy time span range
        if (self.mBusyTime is not None) and period.isPeriodOverlap(
                self.mSpanPeriod):
            list.append(self)

    def expandPeriodFB(self, period, list):
        # Cache the busy-time details if not done already
        if not self.mCachedBusyTime:
            self.cacheBusyTime()

        # See if period intersects the busy time span range
        if (self.mBusyTime is not None) and period.isPeriodOverlap(
                self.mSpanPeriod):
            for fb in self.mBusyTime:
                list.append(FreeBusy(fb))

    def cacheBusyTime(self):

        # Clear out any existing cache
        self.mBusyTime = []

        # Get all FREEBUSY items and add those that are BUSY
        min_start = DateTime()
        max_end = DateTime()
        props = self.getProperties()
        result = props.get(definitions.cICalProperty_FREEBUSY, ())
        for iter in result:

            # Check the properties FBTYPE parameter
            type = 0
            is_busy = False
            if iter.hasParameter(definitions.cICalParameter_FBTYPE):

                fbyype = iter.getParameterValue(
                    definitions.cICalParameter_FBTYPE)
                if fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSY:

                    is_busy = True
                    type = FreeBusy.BUSY

                elif fbyype.upper(
                ) == definitions.cICalParameter_FBTYPE_BUSYUNAVAILABLE:

                    is_busy = True
                    type = FreeBusy.BUSYUNAVAILABLE

                elif fbyype.upper(
                ) == definitions.cICalParameter_FBTYPE_BUSYTENTATIVE:

                    is_busy = True
                    type = FreeBusy.BUSYTENTATIVE

                else:

                    is_busy = False
                    type = FreeBusy.FREE

            else:

                # Default is busy when no parameter
                is_busy = True
                type = FreeBusy.BUSY

            # Add this period
            if is_busy:

                multi = iter.getMultiValue()
                if (multi is not None) and (multi.getType()
                                            == Value.VALUETYPE_PERIOD):

                    for o in multi.getValues():

                        # Double-check type
                        period = None
                        if isinstance(o, PeriodValue):
                            period = o

                        # Double-check type
                        if period is not None:

                            self.mBusyTime.append(
                                FreeBusy(type, period.getValue()))

                            if len(self.mBusyTime) == 1:

                                min_start = period.getValue().getStart()
                                max_end = period.getValue().getEnd()

                            else:

                                if min_start > period.getValue().getStart():
                                    min_start = period.getValue().getStart()
                                if max_end < period.getValue().getEnd():
                                    max_end = period.getValue().getEnd()

        # If nothing present, empty the list
        if len(self.mBusyTime) == 0:

            self.mBusyTime = None

        else:

            # Sort the list by period
            self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart().
                                compareDateTime(y.getPeriod().getStart()))

            # Determine range
            start = DateTime()
            end = DateTime()
            if self.mHasStart:
                start = self.mStart
            else:
                start = min_start
            if self.mHasEnd:
                end = self.mEnd
            else:
                end = max_end

            self.mSpanPeriod = Period(start, end)

        self.mCachedBusyTime = True

    def sortedPropertyKeyOrder(self):
        return (
            definitions.cICalProperty_UID,
            definitions.cICalProperty_DTSTART,
            definitions.cICalProperty_DURATION,
            definitions.cICalProperty_DTEND,
        )
Example #4
0
class VFreeBusy(Component):

    propertyCardinality_1 = (
        definitions.cICalProperty_DTSTAMP,
        definitions.cICalProperty_UID,
    )

    propertyCardinality_0_1 = (
        definitions.cICalProperty_CONTACT,
        definitions.cICalProperty_DTSTART,
        definitions.cICalProperty_DTEND,
        definitions.cICalProperty_ORGANIZER,
        definitions.cICalProperty_URL,
    )

    propertyValueChecks = ICALENDAR_VALUE_CHECKS

    def __init__(self, parent=None):
        super(VFreeBusy, self).__init__(parent=parent)
        self.mStart = DateTime()
        self.mHasStart = False
        self.mEnd = DateTime()
        self.mHasEnd = False
        self.mDuration = False
        self.mCachedBusyTime = False
        self.mSpanPeriod = None
        self.mBusyTime = None


    def duplicate(self, parent=None):
        other = super(VFreeBusy, self).duplicate(parent=parent)
        other.mStart = self.mStart.duplicate()
        other.mHasStart = self.mHasStart
        other.mEnd = self.mEnd.duplicate()
        other.mHasEnd = self.mHasEnd
        other.mDuration = self.mDuration
        other.mCachedBusyTime = False
        other.mBusyTime = None
        return other


    def getType(self):
        return definitions.cICalComponent_VFREEBUSY


    def getMimeComponentName(self):
        return itipdefinitions.cICalMIMEComponent_VFREEBUSY


    def finalise(self):
        # Do inherited
        super(VFreeBusy, self).finalise()

        # Get DTSTART
        temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART)
        self.mHasStart = temp is not None
        if self.mHasStart:
            self.mStart = temp

        # Get DTEND
        temp = self.loadValueDateTime(definitions.cICalProperty_DTEND)
        if temp is None:
            # Try DURATION instead
            temp = self.loadValueDuration(definitions.cICalProperty_DURATION)
            if temp is not None:
                self.mEnd = self.mStart + temp
                self.mDuration = True
            else:
                # Force end to start, which will then be fixed to sensible
                # value later
                self.mEnd = self.mStart
        else:
            self.mHasEnd = True
            self.mDuration = False
            self.mEnd = temp


    def fixStartEnd(self):
        # End is always greater than start if start exists
        if self.mHasStart and self.mEnd <= self.mStart:
            # Use the start
            self.mEnd = self.mStart.duplicate()
            self.mDuration = False

            # Adjust to appropiate non-inclusive end point
            if self.mStart.isDateOnly():
                self.mEnd.offsetDay(1)

                # For all day events it makes sense to use duration
                self.mDuration = True
            else:
                # Use end of current day
                self.mEnd.offsetDay(1)
                self.mEnd.setHHMMSS(0, 0, 0)


    def getStart(self):
        return self.mStart


    def hasStart(self):
        return self.mHasStart


    def getEnd(self):
        return self.mEnd


    def hasEnd(self):
        return self.mHasEnd


    def useDuration(self):
        return self.mDuration


    def getSpanPeriod(self):
        return self.mSpanPeriod


    def getBusyTime(self):
        return self.mBusyTime


    def editTiming(self):
        # Updated cached values
        self.mHasStart = False
        self.mHasEnd = False
        self.mDuration = False
        self.mStart.setToday()
        self.mEnd.setToday()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)


    def editTimingStartEnd(self, start, end):
        # Updated cached values
        self.mHasStart = self.mHasEnd = True
        self.mStart = start
        self.mEnd = end
        self.mDuration = False
        self.fixStartEnd()

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the end one day after the start, ignore it
        temp = start.duplicate()
        temp.offsetDay(1)
        if not start.isDateOnly() or end != temp:
            prop = Property(definitions.cICalProperty_DTEND, end)
            self.addProperty(prop)


    def editTimingStartDuration(self, start, duration):
        # Updated cached values
        self.mHasStart = True
        self.mHasEnd = False
        self.mStart = start
        self.mEnd = start + duration
        self.mDuration = True

        # Remove existing DTSTART & DTEND & DURATION & DUE items
        self.removeProperties(definitions.cICalProperty_DTSTART)
        self.removeProperties(definitions.cICalProperty_DTEND)
        self.removeProperties(definitions.cICalProperty_DURATION)
        self.removeProperties(definitions.cICalProperty_DUE)

        # Now create properties
        prop = Property(definitions.cICalProperty_DTSTART, start)
        self.addProperty(prop)

        # If its an all day event and the duration is one day, ignore it
        if (not start.isDateOnly() or (duration.getWeeks() != 0)
                or (duration.getDays() > 1)):
            prop = Property(definitions.cICalProperty_DURATION, duration)
            self.addProperty(prop)


    # Generating info
    def expandPeriodComp(self, period, list):
        # Cache the busy-time details if not done already
        if not self.mCachedBusyTime:
            self.cacheBusyTime()

        # See if period intersects the busy time span range
        if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod):
            list.append(self)


    def expandPeriodFB(self, period, list):
        # Cache the busy-time details if not done already
        if not self.mCachedBusyTime:
            self.cacheBusyTime()

        # See if period intersects the busy time span range
        if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod):
            for fb in self.mBusyTime:
                list.append(FreeBusy(fb))


    def cacheBusyTime(self):

        # Clear out any existing cache
        self.mBusyTime = []

        # Get all FREEBUSY items and add those that are BUSY
        min_start = DateTime()
        max_end = DateTime()
        props = self.getProperties()
        result = props.get(definitions.cICalProperty_FREEBUSY, ())
        for iter in result:

            # Check the properties FBTYPE parameter
            type = 0
            is_busy = False
            if iter.hasParameter(definitions.cICalParameter_FBTYPE):

                fbyype = iter.getParameterValue(definitions.cICalParameter_FBTYPE)
                if fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSY:

                    is_busy = True
                    type = FreeBusy.BUSY

                elif fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSYUNAVAILABLE:

                    is_busy = True
                    type = FreeBusy.BUSYUNAVAILABLE

                elif fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSYTENTATIVE:

                    is_busy = True
                    type = FreeBusy.BUSYTENTATIVE

                else:

                    is_busy = False
                    type = FreeBusy.FREE

            else:

                # Default is busy when no parameter
                is_busy = True
                type = FreeBusy.BUSY

            # Add this period
            if is_busy:

                multi = iter.getMultiValue()
                if (multi is not None) and (multi.getType() == Value.VALUETYPE_PERIOD):

                    for o in multi.getValues():

                        # Double-check type
                        period = None
                        if isinstance(o, PeriodValue):
                            period = o

                        # Double-check type
                        if period is not None:

                            self.mBusyTime.append(FreeBusy(type, period.getValue()))

                            if len(self.mBusyTime) == 1:

                                min_start = period.getValue().getStart()
                                max_end = period.getValue().getEnd()

                            else:

                                if min_start > period.getValue().getStart():
                                    min_start = period.getValue().getStart()
                                if max_end < period.getValue().getEnd():
                                    max_end = period.getValue().getEnd()

        # If nothing present, empty the list
        if len(self.mBusyTime) == 0:

            self.mBusyTime = None

        else:

            # Sort the list by period
            self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart().compareDateTime(y.getPeriod().getStart()))

            # Determine range
            start = DateTime()
            end = DateTime()
            if self.mHasStart:
                start = self.mStart
            else:
                start = min_start
            if self.mHasEnd:
                end = self.mEnd
            else:
                end = max_end

            self.mSpanPeriod = Period(start, end)

        self.mCachedBusyTime = True


    def sortedPropertyKeyOrder(self):
        return (
            definitions.cICalProperty_UID,
            definitions.cICalProperty_DTSTART,
            definitions.cICalProperty_DURATION,
            definitions.cICalProperty_DTEND,
        )