Пример #1
0
def check_support(vevent: icalendar.cal.Event, href: str, calendar: str):
    """test if all icalendar features used in this event are supported,
    raise `UpdateFailed` otherwise.
    :param vevent: event to test
    :param href: href of this event, only used for logging
    """
    rec_id = vevent.get(RECURRENCE_ID)

    if rec_id is not None and rec_id.params.get('RANGE') == THISANDPRIOR:
        raise UpdateFailed(
            'The parameter `THISANDPRIOR` is not (and will not be) '
            'supported by khal (as applications supporting the latest '
            'standard MUST NOT create those. Therefore event {0} from '
            'calendar {1} will not be shown in khal'
            .format(href, calendar)
        )
    rdate = vevent.get('RDATE')
    if rdate is not None and hasattr(rdate, 'params') and rdate.params.get('VALUE') == 'PERIOD':
        raise UpdateFailed(
            '`RDATE;VALUE=PERIOD` is currently not supported by khal. '
            'Therefore event {0} from calendar {1} will not be shown in khal.\n'
            'Please post exemplary events (please remove any private data) '
            'to https://github.com/pimutils/khal/issues/152 .'
            .format(href, calendar)
        )
Пример #2
0
def check_support(vevent: icalendar.cal.Event, href: str, calendar: str):
    """test if all icalendar features used in this event are supported,
    raise `UpdateFailed` otherwise.
    :param vevent: event to test
    :param href: href of this event, only used for logging
    """
    rec_id = vevent.get(RECURRENCE_ID)

    if rec_id is not None and rec_id.params.get('RANGE') == THISANDPRIOR:
        raise UpdateFailed(
            'The parameter `THISANDPRIOR` is not (and will not be) '
            'supported by khal (as applications supporting the latest '
            'standard MUST NOT create those. Therefore event {0} from '
            'calendar {1} will not be shown in khal'
            .format(href, calendar)
        )
    rdate = vevent.get('RDATE')
    if rdate is not None and hasattr(rdate, 'params') and rdate.params.get('VALUE') == 'PERIOD':
        raise UpdateFailed(
            '`RDATE;VALUE=PERIOD` is currently not supported by khal. '
            'Therefore event {0} from calendar {1} will not be shown in khal.\n'
            'Please post exemplary events (please remove any private data) '
            'to https://github.com/pimutils/khal/issues/152 .'
            .format(href, calendar)
        )
Пример #3
0
    def _update_impl(self, vevent: icalendar.cal.Event, href: str,
                     calendar: str) -> None:
        """insert `vevent` into the database

        expand `vevent`'s recurrence rules (if needed) and insert all instance
        in the respective tables
        than insert non-recurring and original recurring (those with an RRULE
        property) events into table `events`
        """
        # TODO FIXME this function is a steaming pile of shit
        rec_id = vevent.get(RECURRENCE_ID)
        if rec_id is None:
            rrange = None
        else:
            rrange = rec_id.params.get('RANGE')

        # testing on datetime.date won't work as datetime is a child of date
        if not isinstance(vevent['DTSTART'].dt, dt.datetime):
            dtype = EventType.DATE
        else:
            dtype = EventType.DATETIME
        if ('TZID' in vevent['DTSTART'].params and dtype == EventType.DATETIME) or \
                getattr(vevent['DTSTART'].dt, 'tzinfo', None):
            recs_table = 'recs_loc'
        else:
            recs_table = 'recs_float'

        thisandfuture = (rrange == THISANDFUTURE)
        if thisandfuture:
            start_shift, duration = calc_shift_deltas(vevent)
            start_shift_seconds = start_shift.days * 3600 * 24 + start_shift.seconds
            duration_seconds = duration.days * 3600 * 24 + duration.seconds

        dtstartend = utils.expand(vevent, href)
        if not dtstartend:
            # Does this event even have dates? Technically it is possible for
            # events to be empty/non-existent by deleting all their recurrences
            # through EXDATE.
            return

        for dtstart, dtend in dtstartend:
            if dtype == EventType.DATE:
                dbstart = utils.to_unix_time(dtstart)
                dbend = utils.to_unix_time(dtend)
            else:
                dbstart = utils.to_unix_time(dtstart)
                dbend = utils.to_unix_time(dtend)

            if rec_id is not None:
                ref = rec_inst = str(utils.to_unix_time(rec_id.dt))
            else:
                rec_inst = str(dbstart)
                ref = PROTO

            if thisandfuture:
                recs_sql_s = (
                    'UPDATE {0} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, ref = ? '
                    'WHERE rec_inst >= ? AND href = ? AND calendar = ?;'.
                    format(recs_table))
                stuple_f = (
                    start_shift_seconds,
                    start_shift_seconds + duration_seconds,
                    ref,
                    rec_inst,
                    href,
                    calendar,
                )
                self.sql_ex(recs_sql_s, stuple_f)
            else:
                recs_sql_s = (
                    'INSERT OR REPLACE INTO {0} '
                    '(dtstart, dtend, href, ref, dtype, rec_inst, calendar)'
                    'VALUES (?, ?, ?, ?, ?, ?, ?);'.format(recs_table))
                stuple_n = (dbstart, dbend, href, ref, dtype, rec_inst,
                            calendar)
                self.sql_ex(recs_sql_s, stuple_n)
Пример #4
0
    def _update_impl(self, vevent: icalendar.cal.Event, href: str, calendar: str) -> None:
        """insert `vevent` into the database

        expand `vevent`'s recurrence rules (if needed) and insert all instance
        in the respective tables
        than insert non-recurring and original recurring (those with an RRULE
        property) events into table `events`
        """
        # TODO FIXME this function is a steaming pile of shit
        rec_id = vevent.get(RECURRENCE_ID)
        if rec_id is None:
            rrange = None
        else:
            rrange = rec_id.params.get('RANGE')

        # testing on datetime.date won't work as datetime is a child of date
        if not isinstance(vevent['DTSTART'].dt, dt.datetime):
            dtype = EventType.DATE
        else:
            dtype = EventType.DATETIME
        if ('TZID' in vevent['DTSTART'].params and dtype == EventType.DATETIME) or \
                getattr(vevent['DTSTART'].dt, 'tzinfo', None):
            recs_table = 'recs_loc'
        else:
            recs_table = 'recs_float'

        thisandfuture = (rrange == THISANDFUTURE)
        if thisandfuture:
            start_shift, duration = calc_shift_deltas(vevent)
            start_shift_seconds = start_shift.days * 3600 * 24 + start_shift.seconds
            duration_seconds = duration.days * 3600 * 24 + duration.seconds

        dtstartend = utils.expand(vevent, href)
        if not dtstartend:
            # Does this event even have dates? Technically it is possible for
            # events to be empty/non-existent by deleting all their recurrences
            # through EXDATE.
            return

        for dtstart, dtend in dtstartend:
            if dtype == EventType.DATE:
                dbstart = utils.to_unix_time(dtstart)
                dbend = utils.to_unix_time(dtend)
            else:
                dbstart = utils.to_unix_time(dtstart)
                dbend = utils.to_unix_time(dtend)

            if rec_id is not None:
                ref = rec_inst = str(utils.to_unix_time(rec_id.dt))
            else:
                rec_inst = str(dbstart)
                ref = PROTO

            if thisandfuture:
                recs_sql_s = (
                    'UPDATE {0} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, ref = ? '
                    'WHERE rec_inst >= ? AND href = ? AND calendar = ?;'.format(recs_table))
                stuple_f = (
                    start_shift_seconds, start_shift_seconds + duration_seconds,
                    ref, rec_inst, href, calendar,
                )
                self.sql_ex(recs_sql_s, stuple_f)
            else:
                recs_sql_s = (
                    'INSERT OR REPLACE INTO {0} '
                    '(dtstart, dtend, href, ref, dtype, rec_inst, calendar)'
                    'VALUES (?, ?, ?, ?, ?, ?, ?);'.format(recs_table))
                stuple_n = (dbstart, dbend, href, ref, dtype, rec_inst, calendar)
                self.sql_ex(recs_sql_s, stuple_n)
Пример #5
0
    def _processEventAlarms(self, event: icalendar.cal.Event,
                            eventDatetime: datetime.datetime):

        # Create current datetime object.
        currentUTCTime = time.time()
        currentDatetime = datetime.datetime.utcfromtimestamp(currentUTCTime)
        currentDatetime = currentDatetime.replace(tzinfo=pytz.UTC)

        for alarm in event.walk("VALARM"):

            if "TRIGGER" in alarm:

                # Get time delta when reminder is triggered.
                trigger = alarm.get("TRIGGER")

                # Get time when reminder is triggered.
                # When trigger time is a delta, then calculate the actual time.
                if type(trigger.dt) == datetime.timedelta:
                    triggerDatetime = eventDatetime + trigger.dt

                # When trigger time is an actual time, use this.
                elif type(trigger.dt) == datetime.datetime:
                    triggerDatetime = trigger.dt

                    # Use the same timezone as the event when
                    # no is given.
                    if triggerDatetime.tzinfo is None:
                        triggerDatetime = triggerDatetime.replace(
                            tzinfo=eventDatetime.tzinfo)

                # When trigger time is only a date, start at midnight,
                # however, use the same timezone as the event.
                elif type(trigger.dt) == datetime.date:
                    triggerUTCTime = calendar.timegm(trigger.dt.timetuple())
                    triggerDatetime = datetime.datetime.utcfromtimestamp(
                        triggerUTCTime)
                    # Since we just overwrite the timezone here, we do not
                    # care about conversion from UTC since the new datetime
                    # object starts at midnight in the given timezone.
                    triggerDatetime = triggerDatetime.replace(
                        tzinfo=eventDatetime.tzinfo)

                else:
                    logging.error("[%s] Error: Do not know how to handle " %
                                  self.fileName +
                                  "trigger type '%s'." % trigger.dt.__class__)
                    continue

                # Uid of event is needed.
                uid = event.get("UID")

                # Get time when event starts.
                dtstart = event.get("DTSTART")

                # Check if we already triggered an alarm for the event
                # with this uid and the given alarm trigger time.
                if (uid, triggerDatetime) in self.alreadyTriggered:
                    break

                # Check if the alarm trigger time lies in the past but not
                # more than 1 day.
                if ((currentDatetime - self.timedelta1day) <= triggerDatetime
                        <= currentDatetime):

                    title = event.get("SUMMARY")

                    # Get description if event has one.
                    evDescription = ""
                    if "DESCRIPTION" in event:
                        evDescription = event.get("DESCRIPTION")

                    # Get location if event has one.
                    location = ""
                    if "LOCATION" in event:
                        location = event.get("LOCATION")

                    # Create the utc unix timestamp for the start of the event.
                    unixStart = datetime.datetime.utcfromtimestamp(0)
                    unixStart = unixStart.replace(tzinfo=pytz.UTC)

                    utcDtstart = int(
                        (eventDatetime - unixStart).total_seconds())

                    # Create the utc unix timestamp for the reminder trigger.
                    utcTrigger = int(
                        (triggerDatetime - unixStart).total_seconds())

                    eventDateStr = time.strftime("%D %H:%M:%S",
                                                 time.localtime(utcDtstart))
                    msg = "Reminder for event '%s' at %s" % (title,
                                                             eventDateStr)

                    # Create sensor alert.
                    sensorAlert = SensorObjSensorAlert()
                    sensorAlert.clientSensorId = self.id
                    sensorAlert.state = 1
                    sensorAlert.hasOptionalData = True
                    sensorAlert.optionalData = {
                        "message": msg,
                        "calendar": self.name,
                        "type": "reminder",
                        "title": title,
                        "description": evDescription,
                        "location": location,
                        "trigger": utcTrigger,
                        "start": utcDtstart
                    }
                    sensorAlert.changeState = False
                    sensorAlert.hasLatestData = False
                    sensorAlert.dataType = SensorDataType.NONE

                    self.reminderAlertQueue.put(sensorAlert)

                    # Store the event uid and the alarm trigger time
                    # as already triggered.
                    self.alreadyTriggered.add((uid, triggerDatetime))
Пример #6
0
    def _processEvent(self, event: icalendar.cal.Event):

        # Get time when event starts.
        dtstart = event.get("DTSTART")

        # Get current time in timezone of event.
        eventDatetime = None

        # Create a datetime starting at midnight
        # if we have an "all day" event. This event is in the local
        # timezone.
        if type(dtstart.dt) == datetime.date:
            eventUTCTime = calendar.timegm(dtstart.dt.timetuple())
            eventDatetime = datetime.datetime.utcfromtimestamp(eventUTCTime)
            # Since we just overwrite the timezone here, we do not
            # care about conversion from UTC since the new datetime
            # object starts at midnight in the given timezone.
            eventDatetime = eventDatetime.replace(tzinfo=dateutil.tz.tzlocal())

        # Copy the date time if we have a "normal" event.
        elif type(dtstart.dt) == datetime.datetime:
            eventDatetime = dtstart.dt
            if eventDatetime.tzinfo is None:
                eventDatetime = eventDatetime.replace(tzinfo=pytz.UTC)

        else:
            logging.debug("[%s] Do not know how to handle type '%s' " %
                          (self.fileName, dtstart.dt.__class__) +
                          "of event start.")
            return

        # Create current datetime object.
        currentUTCTime = time.time()
        currentDatetime = datetime.datetime.utcfromtimestamp(currentUTCTime)
        currentDatetime = currentDatetime.replace(tzinfo=pytz.UTC)

        # Process rrule if event has one (rrule means event is repeating).
        if "RRULE" in event:

            eventRule = event.get("RRULE")

            if "UNTIL" in eventRule:
                # Sometimes the rrule will fail to parse the event rule if
                # we have a mix of "date times" with timezone and an
                # "until" without it.
                if type(eventRule.get("until")[0]) == datetime.datetime:
                    timezone = eventRule.get("until")[0].tzinfo

                    # "RRULE values must be specified in UTC when
                    # DTSTART is timezone-aware"
                    if timezone is None:
                        eventRule["UNTIL"][0] = eventRule["UNTIL"][0].replace(
                            tzinfo=pytz.UTC)

                # Since date objects do not have a timezone but rrule needs
                # one, we replace the date object with a datetime object
                # in UTC time.
                elif type(eventRule.get("until")[0]) == datetime.date:
                    tempUntil = eventRule.get("until")[0]
                    ruleUTCTime = calendar.timegm(tempUntil.timetuple())
                    ruleDatetime = datetime.datetime.utcfromtimestamp(
                        ruleUTCTime)
                    ruleDatetime = ruleDatetime.replace(tzinfo=pytz.UTC)
                    eventRule["UNTIL"][0] = ruleDatetime

            # Use python dateutil for parsing the rrule.
            eventDatetimeAfter = None
            eventDatetimeBefore = None

            try:
                rrset = dateutil.rrule.rruleset()

                rule_str = eventRule.to_ical().decode("ascii")
                rrulestr = dateutil.rrule.rrulestr(rule_str,
                                                   dtstart=eventDatetime)
                rrset.rrule(rrulestr)

                # Get first event that occurs before
                # and after the current time.
                eventDatetimeAfter = rrset.after(currentDatetime)
                eventDatetimeBefore = rrset.before(currentDatetime)

            except:
                logging.exception("[%s] Not able to parse rrule for '%s'." %
                                  (self.fileName, event.get("SUMMARY")))

            # Process the event alarms for the first event occurring after the
            # current time.
            if eventDatetimeAfter:

                self._processEventAlarms(event, eventDatetimeAfter)

            # Process the event alarms for the first event occurring before the
            # current time (needed because of edge cases with alarms 0 seconds
            # before event). But only check event if it is not older than
            # 10 minutes.
            if eventDatetimeBefore:
                if (eventDatetimeBefore >=
                    (currentDatetime - self.timedelta10min)):
                    self._processEventAlarms(event, eventDatetimeBefore)

        # Process "normal" events.
        else:
            # Check if the event is in the past (minus 10 minutes).
            if eventDatetime <= (currentDatetime - self.timedelta10min):
                return

            self._processEventAlarms(event, eventDatetime)