Beispiel #1
0
    def runConversionTest(self, expectedZone, icalZone):
        dt = datetime.datetime(2004, 10, 11, 13, 22, 21, tzinfo=icalZone)
        convertedZone = convertToICUtzinfo(self.view, dt).tzinfo
        self.failUnless(isinstance(convertedZone, (ICUtzinfo, FloatingTZ)))
        self.failUnlessEqual(expectedZone, convertedZone)

        dt = datetime.datetime(2004, 4, 11, 13, 9, 56, tzinfo=icalZone)
        convertedZone = convertToICUtzinfo(self.view, dt).tzinfo
        self.failUnless(isinstance(convertedZone, (ICUtzinfo, FloatingTZ)))
        self.failUnlessEqual(expectedZone, convertedZone)
Beispiel #2
0
    def runConversionTest(self, expectedZone, icalZone):
        dt = datetime.datetime(2004, 10, 11, 13, 22, 21, tzinfo=icalZone)
        convertedZone = convertToICUtzinfo(self.view, dt).tzinfo
        self.failUnless(isinstance(convertedZone, (ICUtzinfo, FloatingTZ)))
        self.failUnlessEqual(expectedZone, convertedZone)

        dt = datetime.datetime(2004, 4, 11, 13, 9, 56, tzinfo=icalZone)
        convertedZone = convertToICUtzinfo(self.view, dt).tzinfo
        self.failUnless(isinstance(convertedZone, (ICUtzinfo, FloatingTZ)))
        self.failUnlessEqual(expectedZone, convertedZone)
Beispiel #3
0
    def convertDatetime(dt):
        # coerce timezones based on coerceTzinfo
        if coerceTzinfo is not None:
            dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo)

        # ... and make sure we return something with an ICUtzinfo
        return convertToICUtzinfo(view, dt)
Beispiel #4
0
    def convertDatetime(dt):
        # coerce timezones based on coerceTzinfo
        if coerceTzinfo is not None:
            dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo)

        # ... and make sure we return something with an ICUtzinfo
        return convertToICUtzinfo(view, dt)
Beispiel #5
0
def deserialize_date(typeinfo, rv, text):
    return convertToICUtzinfo(rv, dateutilparser(text))
def deserialize_date(typeinfo, rv, text):
    return convertToICUtzinfo(rv, dateutilparser(text))
def whenToDatetime(rv, when):
    return convertToICUtzinfo(rv, dateutilparser(when))
Beispiel #8
0
def whenToDatetime(rv, when):
    return convertToICUtzinfo(rv, dateutilparser(when))
Beispiel #9
0
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, newItemParent):
    view = newItemParent.itsView

    itemIsNew = False
    newStamps = []

    # by default, we'll create a new item, not change existing items
    itemChangeCallback = None

    # store up all attributes in a dictionary ...
    changesDict = {}

    # ... and define a shorthand for updating it
    def change(attr, value):
        changesDict[attr.name] = value

    # rruleset and userReminderInterval/userReminderTime must
    # be set last....
    changeLast = []

    # values that apply to VEVENT and VTODO ...
    summary = vobj.getChildValue("summary", u"")
    description = vobj.getChildValue("description")
    status = vobj.getChildValue("status", "").lower()
    duration = vobj.getChildValue("duration")
    uid = vobj.getChildValue("uid")
    rruleset = vobj.rruleset
    recurrenceID = vobj.getChildValue("recurrence_id")  # ... uh, sorta
    completed = vobj.getChildValue("completed")

    def convertDatetime(dt):
        # coerce timezones based on coerceTzinfo
        if coerceTzinfo is not None:
            dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo)

        # ... and make sure we return something with an ICUtzinfo
        return convertToICUtzinfo(view, dt)

    reminderDelta = None
    reminderAbsoluteTime = None

    try:
        reminderValue = vobj.valarm.trigger.value
    except AttributeError:
        pass
    else:
        if type(reminderValue) is datetime.datetime:
            reminderAbsoluteTime = convertDatetime(reminderValue)
        else:
            assert type(reminderValue) is datetime.timedelta
            reminderDelta = reminderValue

    if vobj.name == "VEVENT":

        if DEBUG:
            logger.debug("got VEVENT %s", vobj)

        newStamps.append(EventStamp)
        dtstart = vobj.getChildValue("dtstart")

        if status in ("confirmed", "tentative"):
            pass
        elif status == "cancelled":  # Chandler doesn't have CANCELLED
            status = "fyi"
        else:
            status = "confirmed"

        if EventStamp.transparency.name not in filters:
            change(EventStamp.transparency, status)

        location = vobj.getChildValue("location")
        if location:
            change(EventStamp.location, Calendar.Location.getLocation(view, location))

    elif vobj.name == "VTODO":

        if DEBUG:
            logger.debug("got VEVENT %s", vobj)

        newStamps.append(TaskStamp)

        # VTODO with a DUE ==> EventTask
        due = vobj.getChildValue("due")
        if due is not None:
            newStamps.append(EventStamp)
            dtstart = due

    else:
        assert False, "vobj %s should always be VEVENT or VTODO" % (vobj,)

    # Save changes applicable to both events & tasks ....

    # SUMMARY <-> {EventStamp,TaskStamp}.summary
    if summary is not None:
        change(newStamps[0].summary, summary)

    # DESCRIPTION <-> body
    if description is not None:
        change(Note.body, description)

    # Absolute time reminders
    if reminderAbsoluteTime is not None and Remindable.reminders.name not in filters:
        changeLast.append(lambda item: setattr(item, Remindable.userReminderTime.name, reminderAbsoluteTime))
    # Custom properties/parameters
    ignoredProperties = {}
    ignoredParameters = {}
    for line in vobj.lines():
        name = line.name.lower()
        if name not in attributesUnderstood:
            line.transformFromNative()
            if not line.encoded and line.behavior:
                line.behavior.encode(line)
            ignoredProperties[name] = line.value
        params = u""
        for key, paramvals in line.params.iteritems():
            if key.lower() not in parametersUnderstood:
                vals = map(vobject.base.dquoteEscape, paramvals)
                params += ";" + key + "=" + ",".join(vals)
        if len(params) > 0:
            ignoredParameters[name] = params

    change(Note.icalendarProperties, ignoredProperties)
    change(Note.icalendarParameters, ignoredParameters)

    # See if we have a corresponding item already
    item = utility.findUID(view, uid)

    if item is not None:
        if DEBUG:
            logger.debug("matched UID %s with %s", uid, item)
    else:
        try:
            # See if uid is a valid repository UUID, if so we'll
            # go ahead and use it for the new item's UUID.
            uuid = UUID(uid)
        except ValueError:
            # Not in valid UUID format, so hash the icaluid to
            # generate a 16-byte string we can use for uuid
            uuid = UUID(md5.new(uid).digest())
            logger.info("Converted icalUID '%s' to UUID '%s'", uid, str(uuid))

        # If there is already an item with this UUID, use it,
        # otherwise we'll create one later
        item = view.findUUID(uuid)

        if item is not None:
            item.icalUID = unicode(uuid)

    if EventStamp in newStamps:
        dtend = vobj.getChildValue("dtend")

        isDate = type(dtstart) == date

        # RFC2445 allows VEVENTs without DTSTART, but it's hard to guess
        # what that would mean, so we won't catch an exception if there's no
        # dtstart.
        anyTime = getattr(dtstart, "x_osaf_anytime_param", None) == "TRUE"

        if duration is None:

            def getDifference(left, right):
                leftIsDate = type(left) == date
                rightIsDate = type(right) == date

                if leftIsDate:
                    if rightIsDate:
                        return left - right
                    else:
                        left = TimeZone.forceToDateTime(view, left)

                elif rightIsDate:
                    right = TimeZone.forceToDateTime(view, right)

                return makeNaiveteMatch(view, left, right.tzinfo) - right

            if dtend is not None:
                duration = getDifference(dtend, dtstart)
            elif anyTime or isDate:
                duration = oneDay
            else:
                duration = datetime.timedelta(0)

        if isDate:
            dtstart = TimeZone.forceToDateTime(view, dtstart)
            # convert to Chandler's notion of all day duration
            duration -= oneDay
        elif dtstart.tzinfo is not None and promptForTimezone:
            # got a timezoned event, prompt (non-modally) to turn on
            # timezones
            app = wx.GetApp()
            if app is not None:

                def ShowTimezoneDialogCallback():
                    ShowTurnOnTimezonesDialog(view=app.UIRepositoryView)

                app.PostAsyncEvent(ShowTimezoneDialogCallback)
            promptForTimezone = False

        dtstart = convertDatetime(dtstart)
        tzinfo = dtstart.tzinfo

        if anyTime:
            change(EventStamp.anyTime, True)
            change(EventStamp.allDay, False)
        elif isDate:
            # allDay events should have anyTime True, so if the user
            # unselects allDay, the time isn't set to midnight
            change(EventStamp.anyTime, True)
            change(EventStamp.allDay, True)
        else:
            change(EventStamp.allDay, False)
            change(EventStamp.anyTime, False)

        change(EventStamp.startTime, dtstart)
        change(EventStamp.duration, duration)

        if (reminderDelta is not None) and (Remindable.reminders.name not in filters):
            changeLast.append(lambda item: setattr(item, EventStamp.userReminderInterval.name, reminderDelta))
        if item is not None:
            event = EventStamp(item)

            if recurrenceID:
                if type(recurrenceID) == date:
                    recurrenceID = datetime.datetime.combine(recurrenceID, time(tzinfo=tzinfo))
                else:
                    recurrenceID = convertToICUtzinfo(view, makeNaiveteMatch(view, recurrenceID, tzinfo))

                masterEvent = EventStamp(item)
                event = masterEvent.getRecurrenceID(recurrenceID)
                if event is None and hasattr(masterEvent, "startTime"):
                    # Some calendars, notably Oracle, serialize
                    # recurrence-id as UTC, which wreaks havoc with
                    # noTZ mode. So move recurrenceID to the same tzinfo
                    # as the master's dtstart, bug 6830
                    masterTzinfo = masterEvent.startTime.tzinfo
                    tweakedID = recurrenceID.astimezone(masterTzinfo)
                    event = masterEvent.getRecurrenceID(tweakedID)
                if event is None:
                    # just in case the previous didn't work
                    tweakedID = recurrenceID.astimezone(tzinfo)
                    event = masterEvent.getRecurrenceID(tweakedID)

                if event is None:
                    # our recurrenceID didn't match an item we know
                    # about.  This may be because the item is created
                    # by a later modification, a case we're not dealing
                    # with.  For now, just skip it.
                    logger.info("RECURRENCE-ID '%s' didn't match rule.", recurrenceID)
                    return (None, None, promptForTimezone)

                item = event.itsItem
                recurrenceLine = vobj.contents["recurrence-id"][0]
                range = recurrenceLine.params.get("RANGE", ["THIS"])[0]
                if range == "THISANDPRIOR":
                    # ignore THISANDPRIOR changes for now
                    logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " "not supported")
                    return (None, None, promptForTimezone)
                elif range == "THIS":
                    itemChangeCallback = event.changeThis
                    # check if this is a modification to a master event
                    # if so, avoid changing the master's UUID when
                    # creating a modification
                    if event.getMaster() == event:
                        mod = event._cloneEvent()
                        mod.modificationFor = mod.occurrenceFor = event.itsItem
                        if item.hasLocalAttributeValue(EventStamp.occurrenceFor.name):
                            del event.occurrenceFor
                        event = mod
                        item = event.itsItem

                elif range == "THISANDFUTURE":
                    itemChangeCallback = event.changeThisAndFuture
                else:
                    logger.info("RECURRENCE-ID RANGE not recognized. " "RANGE = %s" % range)
                    return (None, None, promptForTimezone)
            else:
                if event.rruleset is not None:
                    # re-creating a recurring item from scratch, delete
                    # old recurrence information
                    # item might not be the master, though, so
                    # get the master, or eventItem will be a deleted
                    # event
                    event = event.getMaster()
                    item = event.itsItem
                    # delete modifications the master has, to avoid
                    # changing the master to a modification with a
                    # different UUID
                    for mod in itertools.imap(EventStamp, event.modifications):
                        # [Bug 7019]
                        # We need to del these because, in the deferred
                        # delete case, we would have deferred items
                        # living on, in the manner of the undead, in
                        # master.modifications (and occurrences). This
                        # would ultimately cause .getNextOccurrence()
                        # to terminate prematurely.
                        del mod.modificationFor
                        del mod.occurrenceFor
                        mod.itsItem.delete()

                    event.removeRecurrence()

                itemChangeCallback = event.changeThis

            if DEBUG:
                logger.debug("Changing event: %s" % str(event))
            assert itemChangeCallback is not None, "Must set itemChangeCallback for EventStamp imports"

        if rruleset is not None:
            # fix for Bug 6994, exdate and rdate timezones need to be
            # converted to ICUtzinfo instances
            for typ in "_rdate", "_exdate":
                setattr(rruleset, typ, [convertDatetime(d) for d in getattr(rruleset, typ, [])])
            ruleSetItem = RecurrenceRuleSet(None, itsView=view)
            ruleSetItem.setRuleFromDateUtil(rruleset)
            changeLast.append(lambda item: setattr(item, EventStamp.rruleset.name, ruleSetItem))

    if TaskStamp in newStamps:

        if Note._triageStatus.name not in filters:
            # Translate status from iCalendar to TaskStamp/ContentItem
            triageStatus = TriageEnum.now

            if status == "completed":
                triageStatus = TriageEnum.done
            elif status == "needs-action":
                change(Note.needsReply, True)
            elif status in ("", "in-process"):
                change(Note.needsReply, False)
            elif status == "cancelled":
                triageStatus = TriageEnum.later

            # @@@ Jeffrey: This may not be right...
            # Set triageStatus and triageStatusChanged together.
            if completed is not None:
                if type(completed) == date:
                    completed = TimeZone.forceToDateTime(view, completed)
            changeLast.append(lambda item: item.setTriageStatus(triageStatus, when=completed))

    itemIsNew = item is None

    if itemIsNew:

        # create a new item
        change(Note.icalUID, uid)

        kind = Note.getKind(view)
        item = kind.instantiateItem(None, newItemParent, uuid, withInitialValues=True)
        itemChangeCallback = item.__setattr__
    else:
        if itemChangeCallback is None:
            itemChangeCallback = item.__setattr__

        # update an existing item
        if rruleset is None and recurrenceID is None and EventStamp(item).rruleset is not None:
            # no recurrenceId or rruleset, but the existing item
            # may have recurrence, so delete it
            EventStamp(item).removeRecurrence()

    for attr, val in changesDict.iteritems():

        # Only change a datetime if it's really different
        # from what the item already has:
        if type(val) is datetime.datetime:
            oldValue = getattr(item, attr, None)
            if oldValue is not None and oldValue == val and oldValue.tzinfo == val.tzinfo:
                continue

        itemChangeCallback(attr, val)

    # ... make sure the stamps are right
    for stamp in EventStamp, TaskStamp:
        if not stamp in newStamps:
            if has_stamp(item, stamp):
                stamp(item).remove()
        else:
            if not has_stamp(item, stamp):
                stamp(item).add()

    # ... and do the final set of changes
    for cb in changeLast:
        cb(item)

    return item, itemIsNew, promptForTimezone
Beispiel #10
0
    def deserialize(cls, view, text, silentFailure=True):
        """
        Parse an ICalendar blob into a list of record sets
        """

        recordSets = {}
        extra = {'forceDateTriage': True}

        calname = None

        # iterate over calendars, usually only one, but more are allowed
        for calendar in vobject.readComponents(text,
                                               validate=False,
                                               ignoreUnreadable=True):
            if calname is None:
                calname = calendar.getChildValue('x_wr_calname')
                if calname is not None:
                    extra['name'] = calname

        masters = {}
        for vobj in getattr(calendar, 'vevent_list', []):
            uid = vobj.getChildValue('uid')
            if vobj.getChildValue('recurrence_id') is None:
                masters[uid] = vobj

        uid_to_uuid_map = {}

        for vobj in chain(getattr(calendar, 'vevent_list', []),
                          getattr(calendar, 'vtodo_list', [])):
            try:
                recurrenceID = vobj.getChildValue('recurrence_id')
                summary = vobj.getChildValue('summary', eim.NoChange)
                description = vobj.getChildValue('description', eim.NoChange)
                status = vobj.getChildValue('status', eim.NoChange)
                duration = vobj.getChildValue('duration')
                uid = vobj.getChildValue('uid')
                dtstart = vobj.getChildValue('dtstart')
                location = vobj.getChildValue('location', eim.NoChange)

                # bug 10821, Google serializes modifications with no master;
                # treat these as normal events, not modifications
                if not masters.get(uid):
                    recurrenceID = None

                # can't just compare recurrenceID and dtstart, timezone could
                # have changed, and comparing floating to non-floating would
                # raise an exception
                if recurrenceID is None:
                    dtstart_changed = True
                elif dtstart is None:
                    dtstart_changed = False
                elif type(recurrenceID) == date or type(dtstart) == date:
                    dtstart_changed = recurrenceID != dtstart
                else:
                    dtstart_changed = (dtstart.tzinfo != recurrenceID.tzinfo
                                       or dtstart != recurrenceID)

                if status is not eim.NoChange:
                    status = status.upper()

                start_obj = getattr(vobj, 'dtstart', None)

                isVtodo = (vobj.name == 'VTODO')
                osafStarred = vobj.getChildValue('x_osaf_starred')
                if osafStarred:
                    osafStarred = (u"TRUE" == osafStarred.upper())

                if dtstart is None or isVtodo:
                    # due takes precedence over dtstart
                    due = vobj.getChildValue('due')
                    if due is not None:
                        dtstart = due
                        start_obj = getattr(vobj, 'due', None)

                anyTime = False
                if dtstart is not None:
                    anyTimeParam = getattr(start_obj, 'x_osaf_anytime_param',
                                           '')
                    anyTime = anyTimeParam.upper() == 'TRUE'

                isDate = type(dtstart) == date
                allDay = isDate and not anyTime

                emitEvent = (dtstart is not None)

                if duration is None:
                    dtend = vobj.getChildValue('dtend')

                    def getDifference(left, right):
                        leftIsDate = (type(left) == date)
                        rightIsDate = (type(right) == date)

                        if leftIsDate:
                            if rightIsDate:
                                return left - right
                            else:
                                left = forceToDateTime(view, left)

                        elif rightIsDate:
                            right = forceToDateTime(view, right)

                        return makeNaiveteMatch(view, left,
                                                right.tzinfo) - right

                    if dtend is not None and dtstart is not None:
                        duration = getDifference(dtend, dtstart)

                    elif anyTime or isDate:
                        duration = timedelta(1)
                    else:
                        duration = timedelta(0)

                # handle the special case of a midnight-to-midnight floating
                # event, treat it as allDay, bug 9579
                if (not isDate and dtstart is not None
                        and dtstart.tzinfo is None
                        and dtstart.time() == midnight and duration.days >= 1
                        and duration == timedelta(duration.days)):
                    allDay = True

                if isDate:
                    dtstart = forceToDateTime(view, dtstart)
                    # originally, duration was converted to Chandler's notion of
                    # all day duration, but this step will be done by the
                    # translator
                    #duration -= oneDay

                if dtstart is not None:
                    dtstart = convertToICUtzinfo(view, dtstart)
                    dtstart = toICalendarDateTime(view, dtstart, allDay,
                                                  anyTime)

                # convert to EIM value
                duration = toICalendarDuration(duration)

                uuid = UUIDFromICalUID(view, uid_to_uuid_map, uid)

                valarm = getattr(vobj, 'valarm', None)

                if valarm is not None:
                    remValue = valarm.getChildValue('trigger')
                    remDuration = valarm.getChildValue('duration')
                    remRepeat = valarm.getChildValue('repeat')
                    remDescription = valarm.getChildValue(
                        'description', "Event Reminder")
                    trigger = None

                    if remValue is not None:
                        if type(remValue) is datetime:
                            icutzinfoValue = convertToICUtzinfo(view, remValue)
                            trigger = toICalendarDateTime(
                                view, icutzinfoValue, False)
                        else:
                            assert type(remValue) is timedelta
                            trigger = toICalendarDuration(remValue)

                    if remDuration is not None:
                        remDuration = toICalendarDuration(remDuration)

                    if remRepeat is not None:
                        remRepeat = int(remRepeat)

                recurrence = {}

                for rule_name in ('rrule', 'exrule'):
                    rules = []
                    for line in vobj.contents.get(rule_name, []):
                        rules.append(line.value)
                    recurrence[rule_name] = (":".join(rules) if len(rules) > 0
                                             else eim.NoChange)

                for date_name in ('rdate', 'exdate'):
                    dates = []
                    for line in vobj.contents.get(date_name, []):
                        dates.extend(line.value)
                    if len(dates) > 0:
                        if not (allDay or anyTime):
                            dates = [
                                convertToICUtzinfo(view, dt) for dt in dates
                            ]
                        dt_value = toICalendarDateTime(view, dates, allDay,
                                                       anyTime)
                    else:
                        dt_value = eim.NoChange
                    recurrence[date_name] = dt_value

                if recurrenceID is not None:
                    range = getattr(vobj.recurrence_id, 'range_param', 'THIS')
                    if range != 'THIS':
                        logger.info("Skipping a THISANDFUTURE or "
                                    "THISANDPRIOR modification")
                        continue

                    dateValue = allDay or anyTime
                    recurrenceID = forceToDateTime(view, recurrenceID)
                    recurrenceID = convertToICUtzinfo(view, recurrenceID)
                    if recurrenceID.tzinfo != view.tzinfo.floating:
                        recurrenceID = recurrenceID.astimezone(view.tzinfo.UTC)
                    rec_string = translator.formatDateTime(
                        view, recurrenceID, dateValue, dateValue)

                    uuid += ":" + rec_string
                    master = masters[uid]
                    uid = eim.Inherit
                    if (master.getChildValue('duration') == vobj.getChildValue(
                            'duration')):
                        duration = eim.Inherit
                    masterAnyTime = (getattr(master.dtstart,
                                             'x_osaf_anytime_param',
                                             '') == 'TRUE')

                    masterAllDay = (not masterAnyTime
                                    and type(master.dtstart.value) == date)

                    if (masterAllDay == allDay and masterAnyTime == anyTime
                            and not dtstart_changed):
                        dtstart = eim.Inherit

                triage = eim.NoChange
                needsReply = eim.NoChange

                if isVtodo and status is not eim.NoChange:
                    status = status.lower()
                    code = vtodo_status_to_triage_code.get(status, "100")
                    completed = vobj.getChildValue('completed')
                    if completed is not None:
                        if type(completed) == date:
                            completed = TimeZone.forceToDateTime(
                                view, completed)
                        timestamp = str(
                            Triageable.makeTriageStatusChangedTime(
                                view, completed))
                    else:
                        timestamp = getattr(vobj.status,
                                            'x_osaf_changed_param', "0.0")
                    auto = getattr(vobj.status, 'x_osaf_auto_param', 'FALSE')
                    auto = ("1" if auto == 'TRUE' else "0")
                    triage = code + " " + timestamp + " " + auto

                    needsReply = (1 if status == 'needs-action' else 0)

                    # VTODO's status doesn't correspond to EventRecord's status
                    status = eim.NoChange

                icalExtra = eim.NoChange
                if not isVtodo:
                    # not processing VTODOs
                    icalExtra = extractUnrecognized(calendar, vobj)
                    if icalExtra is None:
                        icalExtra = ''
                    else:
                        icalExtra = icalExtra.serialize().decode('utf-8')

                records = [
                    model.NoteRecord(
                        uuid,
                        description,  # body
                        uid,  # icalUid
                        None,  # icalProperties
                        None,  # icalParameters
                        icalExtra,  # icalExtra
                    ),
                    model.ItemRecord(
                        uuid,
                        summary,  # title
                        triage,  # triage
                        eim.NoChange,  # createdOn
                        eim.NoChange,  # hasBeenSent (TODO)
                        needsReply,  # needsReply (TODO)
                        eim.NoChange,  # read
                    )
                ]
                if emitEvent:
                    records.append(
                        model.EventRecord(
                            uuid,
                            dtstart,
                            duration,
                            location,
                            recurrence['rrule'],  # rrule
                            recurrence['exrule'],  # exrule
                            recurrence['rdate'],  # rdate
                            recurrence['exdate'],  # exdate
                            status,  # status
                            eim.NoChange  # lastPastOccurrence
                        ))
                if osafStarred:
                    records.append(model.TaskRecord(uuid))

                if valarm is not None:
                    records.append(
                        model.DisplayAlarmRecord(uuid, remDescription, trigger,
                                                 remDuration, remRepeat))
                else:
                    records.append(
                        model.DisplayAlarmRecord(uuid, None, None, None, None))

                recordSets[uuid] = RecordSet(records)

            except vobject.base.VObjectError, e:
                icalendarLines = text.splitlines()
                logger.error(
                    "Exception when importing icalendar, first 300 lines: \n%s"
                    % "\n".join(icalendarLines[:300]))
                logger.exception(
                    "import failed to import one event with exception: %s" %
                    str(e))
                if not silentFailure:
                    raise
Beispiel #11
0
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, 
                      newItemParent):
    view = newItemParent.itsView
    
    itemIsNew = False
    newStamps = []

    # by default, we'll create a new item, not change existing items
    itemChangeCallback = None

    # store up all attributes in a dictionary ...
    changesDict = {}

    # ... and define a shorthand for updating it
    def change(attr, value):
        changesDict[attr.name] = value
        
    # rruleset and userReminderInterval/userReminderTime must
    # be set last....
    changeLast = []

    # values that apply to VEVENT and VTODO ...
    summary     = vobj.getChildValue('summary', u"")
    description = vobj.getChildValue('description')
    status      = vobj.getChildValue('status', "").lower()
    duration    = vobj.getChildValue('duration')
    uid         = vobj.getChildValue('uid')
    rruleset    = vobj.rruleset
    recurrenceID = vobj.getChildValue('recurrence_id') # ... uh, sorta
    completed = vobj.getChildValue('completed')


    def convertDatetime(dt):
        # coerce timezones based on coerceTzinfo
        if coerceTzinfo is not None:
            dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo)

        # ... and make sure we return something with an ICUtzinfo
        return convertToICUtzinfo(view, dt)

    reminderDelta = None
    reminderAbsoluteTime = None

    try:
        reminderValue = vobj.valarm.trigger.value
    except AttributeError:
        pass
    else:
        if type(reminderValue) is datetime.datetime:
            reminderAbsoluteTime = convertDatetime(reminderValue)
        else:
            assert type(reminderValue) is datetime.timedelta
            reminderDelta = reminderValue
            

    if vobj.name == "VEVENT":

        if DEBUG: logger.debug("got VEVENT %s", vobj)
        
        newStamps.append(EventStamp)
        dtstart = vobj.getChildValue('dtstart')

        if status in ('confirmed', 'tentative'):
            pass
        elif status == 'cancelled': #Chandler doesn't have CANCELLED
            status = 'fyi'
        else:
            status = 'confirmed'

        if EventStamp.transparency.name not in filters:
            change(EventStamp.transparency, status)

        location = vobj.getChildValue('location')
        if location:
            change(EventStamp.location,
                   Calendar.Location.getLocation(view, location))
        
    elif vobj.name == "VTODO":

        if DEBUG: logger.debug("got VEVENT %s", vobj)
        
        newStamps.append(TaskStamp)
        
        # VTODO with a DUE ==> EventTask
        due = vobj.getChildValue('due')
        if due is not None:
            newStamps.append(EventStamp)
            dtstart = due
            
    else:
        assert False, "vobj %s should always be VEVENT or VTODO" % (
                            vobj,)


    # Save changes applicable to both events & tasks ....

    # SUMMARY <-> {EventStamp,TaskStamp}.summary
    if summary is not None:
        change(newStamps[0].summary, summary)

    # DESCRIPTION <-> body  
    if description is not None:
        change(Note.body, description)
        
    # Absolute time reminders
    if (reminderAbsoluteTime is not None and
        Remindable.reminders.name not in filters):
        changeLast.append(lambda item: setattr(item,
                                               Remindable.userReminderTime.name, 
                                               reminderAbsoluteTime))
    # Custom properties/parameters                                           
    ignoredProperties = {}
    ignoredParameters = {}
    for line in vobj.lines():
        name = line.name.lower()
        if name not in attributesUnderstood:
            line.transformFromNative()
            if not line.encoded and line.behavior:
                line.behavior.encode(line)
            ignoredProperties[name] = line.value
        params=u''
        for key, paramvals in line.params.iteritems():
            if key.lower() not in parametersUnderstood:
                vals = map(vobject.base.dquoteEscape, paramvals)
                params += ';' + key + '=' + ','.join(vals)
        if len(params) > 0:
            ignoredParameters[name] = params

    change(Note.icalendarProperties, ignoredProperties)
    change(Note.icalendarParameters, ignoredParameters)


    # See if we have a corresponding item already
    item = utility.findUID(view, uid)

    if item is not None:
        if DEBUG: logger.debug("matched UID %s with %s", uid, item)
    else:
        try:
            # See if uid is a valid repository UUID, if so we'll
            # go ahead and use it for the new item's UUID.
            uuid = UUID(uid)
        except ValueError:
            # Not in valid UUID format, so hash the icaluid to
            # generate a 16-byte string we can use for uuid
            uuid = UUID(md5.new(uid).digest())
            logger.info("Converted icalUID '%s' to UUID '%s'", uid,
                str(uuid))

        # If there is already an item with this UUID, use it,
        # otherwise we'll create one later
        item = view.findUUID(uuid)
            
        if item is not None:
            item.icalUID = unicode(uuid)

            
    if EventStamp in newStamps:
        dtend = vobj.getChildValue('dtend')

        isDate = type(dtstart) == date

        # RFC2445 allows VEVENTs without DTSTART, but it's hard to guess
        # what that would mean, so we won't catch an exception if there's no
        # dtstart.
        anyTime = (getattr(dtstart, 'x_osaf_anytime_param', None)
                      == 'TRUE')
        
        if duration is None:
        
            def getDifference(left, right):
                leftIsDate = (type(left) == date)
                rightIsDate = (type(right) == date)
                
                if leftIsDate:
                    if rightIsDate:
                        return left - right
                    else:
                        left = TimeZone.forceToDateTime(view, left)
                        
                elif rightIsDate:
                    right = TimeZone.forceToDateTime(view, right)

                return makeNaiveteMatch(view, left, right.tzinfo) - right
                
            if dtend is not None:
                duration = getDifference(dtend, dtstart)
            elif anyTime or isDate:
                duration = oneDay
            else:
                duration = datetime.timedelta(0)

        if isDate:
            dtstart = TimeZone.forceToDateTime(view, dtstart)
            # convert to Chandler's notion of all day duration
            duration -= oneDay
        elif dtstart.tzinfo is not None and promptForTimezone:
            # got a timezoned event, prompt (non-modally) to turn on
            # timezones
            app = wx.GetApp()
            if app is not None:
                def ShowTimezoneDialogCallback():
                    ShowTurnOnTimezonesDialog(view=app.UIRepositoryView)
                app.PostAsyncEvent(ShowTimezoneDialogCallback)
            promptForTimezone = False

        dtstart = convertDatetime(dtstart)
        tzinfo = dtstart.tzinfo

        if anyTime:
            change(EventStamp.anyTime, True)
            change(EventStamp.allDay, False)
        elif isDate:
            # allDay events should have anyTime True, so if the user
            # unselects allDay, the time isn't set to midnight
            change(EventStamp.anyTime, True)
            change(EventStamp.allDay, True)
        else:
            change(EventStamp.allDay, False)
            change(EventStamp.anyTime, False)

        change(EventStamp.startTime, dtstart)
        change(EventStamp.duration, duration)

        if ((reminderDelta is not None) and
            (Remindable.reminders.name not in filters)):
                changeLast.append(
                    lambda item:setattr(item,
                                        EventStamp.userReminderInterval.name, 
                                        reminderDelta))
        if item is not None:
            event = EventStamp(item)

            if recurrenceID:
                if type(recurrenceID) == date:
                    recurrenceID = datetime.datetime.combine(
                                                recurrenceID,
                                                time(tzinfo=tzinfo))
                else:
                    recurrenceID = convertToICUtzinfo(view, 
                                       makeNaiveteMatch(view, recurrenceID,
                                       tzinfo))
                
                masterEvent = EventStamp(item)    
                event = masterEvent.getRecurrenceID(recurrenceID)
                if event is None and hasattr(masterEvent, 'startTime'):
                    # Some calendars, notably Oracle, serialize
                    # recurrence-id as UTC, which wreaks havoc with 
                    # noTZ mode. So move recurrenceID to the same tzinfo
                    # as the master's dtstart, bug 6830
                    masterTzinfo = masterEvent.startTime.tzinfo
                    tweakedID = recurrenceID.astimezone(masterTzinfo)
                    event = masterEvent.getRecurrenceID(tweakedID)
                if event is None:
                    # just in case the previous didn't work
                    tweakedID = recurrenceID.astimezone(tzinfo)
                    event = masterEvent.getRecurrenceID(tweakedID)
                    
                if event is None:
                    # our recurrenceID didn't match an item we know
                    # about.  This may be because the item is created
                    # by a later modification, a case we're not dealing
                    # with.  For now, just skip it.
                    logger.info("RECURRENCE-ID '%s' didn't match rule.",
                                recurrenceID)
                    return (None, None, promptForTimezone)

                item = event.itsItem
                recurrenceLine = vobj.contents['recurrence-id'][0]
                range = recurrenceLine.params.get('RANGE', ['THIS'])[0]
                if range == 'THISANDPRIOR':
                    # ignore THISANDPRIOR changes for now
                    logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " \
                                "not supported")
                    return (None, None, promptForTimezone)
                elif range == 'THIS':
                    itemChangeCallback = event.changeThis
                    # check if this is a modification to a master event
                    # if so, avoid changing the master's UUID when
                    # creating a modification
                    if event.getMaster() == event:
                        mod = event._cloneEvent()
                        mod.modificationFor = mod.occurrenceFor = event.itsItem
                        if item.hasLocalAttributeValue(
                                        EventStamp.occurrenceFor.name):
                            del event.occurrenceFor
                        event = mod
                        item = event.itsItem
                    
                elif range == 'THISANDFUTURE':
                    itemChangeCallback = event.changeThisAndFuture
                else:
                    logger.info("RECURRENCE-ID RANGE not recognized. " \
                                "RANGE = %s" % range)
                    return (None, None, promptForTimezone)
            else:
                if event.rruleset is not None:
                    # re-creating a recurring item from scratch, delete 
                    # old recurrence information
                    # item might not be the master, though, so
                    # get the master, or eventItem will be a deleted
                    # event
                    event = event.getMaster()
                    item = event.itsItem
                    # delete modifications the master has, to avoid
                    # changing the master to a modification with a
                    # different UUID
                    for mod in itertools.imap(EventStamp, event.modifications):
                        # [Bug 7019]
                        # We need to del these because, in the deferred
                        # delete case, we would have deferred items
                        # living on, in the manner of the undead, in
                        # master.modifications (and occurrences). This
                        # would ultimately cause .getNextOccurrence()
                        # to terminate prematurely.
                        del mod.modificationFor
                        del mod.occurrenceFor
                        mod.itsItem.delete()

                    event.removeRecurrence()
                    
                itemChangeCallback = event.changeThis

            if DEBUG: logger.debug("Changing event: %s" % str(event))
            assert itemChangeCallback is not None, \
                   "Must set itemChangeCallback for EventStamp imports"
                

        if rruleset is not None:
            # fix for Bug 6994, exdate and rdate timezones need to be
            # converted to ICUtzinfo instances
            for typ in '_rdate', '_exdate':
                setattr(rruleset, typ, [convertDatetime(d) for d in
                                        getattr(rruleset, typ, [])  ])
            ruleSetItem = RecurrenceRuleSet(None, itsView=view)
            ruleSetItem.setRuleFromDateUtil(rruleset)
            changeLast.append(lambda item: setattr(item,
                                                   EventStamp.rruleset.name,
                                                   ruleSetItem))
        
    if TaskStamp in newStamps:
        
        if Note._triageStatus.name not in filters:
            # Translate status from iCalendar to TaskStamp/ContentItem
            triageStatus=TriageEnum.now
            
            if status == "completed":
                triageStatus = TriageEnum.done
            elif status == "needs-action":
                change(Note.needsReply, True)
            elif status in ("", "in-process"):
                change(Note.needsReply, False)
            elif status == "cancelled":
                triageStatus = TriageEnum.later

            # @@@ Jeffrey: This may not be right...
            # Set triageStatus and triageStatusChanged together.
            if completed is not None:
                if type(completed) == date:
                    completed = TimeZone.forceToDateTime(view, completed)
            changeLast.append(lambda item: item.setTriageStatus(triageStatus, 
                                                                when=completed))
            

    itemIsNew = (item is None)
            
    if itemIsNew:

        # create a new item
        change(Note.icalUID, uid)

        kind = Note.getKind(view)
        item = kind.instantiateItem(None, newItemParent, uuid,
                                    withInitialValues=True)
        itemChangeCallback = item.__setattr__
    else:
        if itemChangeCallback is None:
            itemChangeCallback = item.__setattr__
        
        # update an existing item
        if (rruleset is None and recurrenceID is None
           and EventStamp(item).rruleset is not None):
            # no recurrenceId or rruleset, but the existing item
            # may have recurrence, so delete it
            EventStamp(item).removeRecurrence()

    for attr, val in changesDict.iteritems():
    
        # Only change a datetime if it's really different
        # from what the item already has:
        if type(val) is datetime.datetime:
             oldValue = getattr(item, attr, None)
             if (oldValue is not None and 
                 oldValue == val and
                 oldValue.tzinfo == val.tzinfo):
                continue
    
        itemChangeCallback(attr, val)

    # ... make sure the stamps are right
    for stamp in EventStamp, TaskStamp:
        if not stamp in newStamps:
            if has_stamp(item, stamp):
                stamp(item).remove()
        else:
            if not has_stamp(item, stamp):
                stamp(item).add()

    # ... and do the final set of changes
    for cb in changeLast:
        cb(item)
        
            
    return item, itemIsNew, promptForTimezone
Beispiel #12
0
    def deserialize(cls, view, text, silentFailure=True):
        """
        Parse an ICalendar blob into a list of record sets
        """

        recordSets = {}
        extra = {'forceDateTriage' : True}

        calname = None
    
        # iterate over calendars, usually only one, but more are allowed
        for calendar in vobject.readComponents(text, validate=False,
                                               ignoreUnreadable=True):
            if calname is None:
                calname = calendar.getChildValue('x_wr_calname')
                if calname is not None:
                    extra['name'] = calname
    
        masters = {}
        for vobj in getattr(calendar, 'vevent_list', []):
            uid = vobj.getChildValue('uid')
            if vobj.getChildValue('recurrence_id') is None:
                masters[uid] = vobj

        uid_to_uuid_map = {}
        
        for vobj in chain(
                                getattr(calendar, 'vevent_list', []),
                                getattr(calendar, 'vtodo_list', [])
                            ):
            try:
                recurrenceID = vobj.getChildValue('recurrence_id')
                summary      = vobj.getChildValue('summary', eim.NoChange)
                description  = vobj.getChildValue('description', eim.NoChange)
                status       = vobj.getChildValue('status', eim.NoChange)
                duration     = vobj.getChildValue('duration')
                uid          = vobj.getChildValue('uid')
                dtstart      = vobj.getChildValue('dtstart')
                location     = vobj.getChildValue('location', eim.NoChange)
                
                # bug 10821, Google serializes modifications with no master;
                # treat these as normal events, not modifications
                if not masters.get(uid):
                    recurrenceID = None
                
                # can't just compare recurrenceID and dtstart, timezone could
                # have changed, and comparing floating to non-floating would
                # raise an exception
                if recurrenceID is None:
                    dtstart_changed = True
                elif dtstart is None:
                    dtstart_changed = False
                elif type(recurrenceID) == date or type(dtstart) == date:
                    dtstart_changed = recurrenceID != dtstart
                else:
                    dtstart_changed = (dtstart.tzinfo != recurrenceID.tzinfo or
                                       dtstart != recurrenceID)
                    
                if status is not eim.NoChange:
                    status = status.upper()

                start_obj = getattr(vobj, 'dtstart', None)

                isVtodo = (vobj.name == 'VTODO')
                osafStarred = vobj.getChildValue('x_osaf_starred')
                if osafStarred:
                    osafStarred = (u"TRUE" == osafStarred.upper())

                if dtstart is None or isVtodo:
                    # due takes precedence over dtstart
                    due = vobj.getChildValue('due')
                    if due is not None:
                        dtstart = due
                        start_obj = getattr(vobj, 'due', None)
                    
                anyTime = False
                if dtstart is not None:
                    anyTimeParam = getattr(start_obj, 'x_osaf_anytime_param',
                                           '')
                    anyTime = anyTimeParam.upper() == 'TRUE'

                isDate = type(dtstart) == date
                allDay = isDate and not anyTime

                
                emitEvent = (dtstart is not None)
                
                if duration is None:
                    dtend = vobj.getChildValue('dtend')
                
                    def getDifference(left, right):
                        leftIsDate = (type(left) == date)
                        rightIsDate = (type(right) == date)
                        
                        if leftIsDate:
                            if rightIsDate:
                                return left - right
                            else:
                                left = forceToDateTime(view, left)
                                
                        elif rightIsDate:
                            right = forceToDateTime(view, right)
        
                        return makeNaiveteMatch(view,
                                                left, right.tzinfo) - right
                        
                    if dtend is not None and dtstart is not None:
                        duration = getDifference(dtend, dtstart)
                            
                    elif anyTime or isDate:
                        duration = timedelta(1)
                    else:
                        duration = timedelta(0)

                # handle the special case of a midnight-to-midnight floating
                # event, treat it as allDay, bug 9579
                if (not isDate and dtstart is not None and
                      dtstart.tzinfo is None and dtstart.time() == midnight and
                      duration.days >= 1 and
                      duration == timedelta(duration.days)):
                    allDay = True
                
                if isDate:
                    dtstart = forceToDateTime(view, dtstart)
                    # originally, duration was converted to Chandler's notion of
                    # all day duration, but this step will be done by the
                    # translator
                    #duration -= oneDay

                if dtstart is not None:
                    dtstart = convertToICUtzinfo(view, dtstart)
                    dtstart = toICalendarDateTime(view, dtstart, allDay, anyTime)
    
                # convert to EIM value
                duration = toICalendarDuration(duration)                

                uuid = UUIDFromICalUID(view, uid_to_uuid_map, uid)

                valarm = getattr(vobj, 'valarm', None)
                
                if valarm is not None:
                    remValue        = valarm.getChildValue('trigger')
                    remDuration     = valarm.getChildValue('duration')
                    remRepeat       = valarm.getChildValue('repeat')
                    remDescription  = valarm.getChildValue('description',
                                                           "Event Reminder")
                    trigger = None
                    
                    if remValue is not None:
                        if type(remValue) is datetime:
                            icutzinfoValue = convertToICUtzinfo(view, remValue)
                            trigger = toICalendarDateTime(view, icutzinfoValue, False)
                        else:
                            assert type(remValue) is timedelta
                            trigger = toICalendarDuration(remValue)
                            
                    if remDuration is not None:
                        remDuration = toICalendarDuration(remDuration)
                        
                    if remRepeat is not None:
                        remRepeat = int(remRepeat)

                recurrence = {}
            
                for rule_name in ('rrule', 'exrule'):
                    rules = []
                    for line in vobj.contents.get(rule_name, []):
                        rules.append(line.value)
                    recurrence[rule_name] = (":".join(rules) if len(rules) > 0 
                                             else eim.NoChange)
            
                for date_name in ('rdate', 'exdate'):
                    dates = []
                    for line in vobj.contents.get(date_name, []):
                        dates.extend(line.value)
                    if len(dates) > 0:
                        if not (allDay or anyTime):
                            dates = [convertToICUtzinfo(view, dt)
                                     for dt in dates]
                        dt_value = toICalendarDateTime(view, dates, allDay, anyTime)
                    else:
                        dt_value = eim.NoChange
                    recurrence[date_name] = dt_value
            

                if recurrenceID is not None:
                    range = getattr(vobj.recurrence_id, 'range_param', 'THIS')
                    if range != 'THIS':
                        logger.info("Skipping a THISANDFUTURE or "
                                    "THISANDPRIOR modification")
                        continue
                    
                    dateValue = allDay or anyTime
                    recurrenceID = forceToDateTime(view, recurrenceID)
                    recurrenceID = convertToICUtzinfo(view, recurrenceID)
                    if recurrenceID.tzinfo != view.tzinfo.floating:
                        recurrenceID = recurrenceID.astimezone(view.tzinfo.UTC)
                    rec_string = translator.formatDateTime(view, recurrenceID,
                                                           dateValue, dateValue)

                    uuid += ":" + rec_string
                    master = masters[uid]
                    uid = eim.Inherit
                    if (master.getChildValue('duration') == 
                          vobj.getChildValue('duration')):
                        duration = eim.Inherit
                    masterAnyTime = (getattr(master.dtstart, 
                                          'x_osaf_anytime_param', '') == 'TRUE')
                    
                    masterAllDay = (not masterAnyTime and 
                                    type(master.dtstart.value) == date)
                    
                    if (masterAllDay == allDay and masterAnyTime == anyTime and
                        not dtstart_changed):
                        dtstart = eim.Inherit
                
                triage = eim.NoChange
                needsReply = eim.NoChange
                
                if isVtodo and status is not eim.NoChange:
                    status = status.lower()
                    code = vtodo_status_to_triage_code.get(status, "100")
                    completed = vobj.getChildValue('completed')
                    if completed is not None:
                        if type(completed) == date:
                            completed = TimeZone.forceToDateTime(view, completed)
                        timestamp = str(Triageable.makeTriageStatusChangedTime(view, completed))
                    else:
                        timestamp = getattr(vobj.status, 'x_osaf_changed_param',
                                            "0.0")
                    auto = getattr(vobj.status, 'x_osaf_auto_param', 'FALSE')
                    auto = ("1" if auto == 'TRUE' else "0")
                    triage =  code + " " + timestamp + " " + auto
                    
                    needsReply = (1 if status == 'needs-action' else 0)

                    # VTODO's status doesn't correspond to EventRecord's status
                    status = eim.NoChange
                
                icalExtra = eim.NoChange
                if not isVtodo:
                    # not processing VTODOs
                    icalExtra = extractUnrecognized(calendar, vobj)
                    if icalExtra is None:
                        icalExtra = ''
                    else:
                        icalExtra = icalExtra.serialize().decode('utf-8')

                records = [model.NoteRecord(uuid,
                                            description,  # body
                                            uid,          # icalUid
                                            None,         # icalProperties
                                            None,         # icalParameters
                                            icalExtra,    # icalExtra
                                            ),
                           model.ItemRecord(uuid, 
                                            summary,        # title
                                            triage,         # triage
                                            eim.NoChange,   # createdOn
                                            eim.NoChange,   # hasBeenSent (TODO)
                                            needsReply,     # needsReply (TODO)
                                            eim.NoChange,   # read
                                            )]
                if emitEvent:
                    records.append(model.EventRecord(uuid,
                                            dtstart,
                                            duration,
                                            location,
                                            recurrence['rrule'],   # rrule
                                            recurrence['exrule'],  # exrule
                                            recurrence['rdate'],   # rdate
                                            recurrence['exdate'],  # exdate
                                            status,                # status
                                            eim.NoChange    # lastPastOccurrence
                                            ))
                if osafStarred:
                    records.append(model.TaskRecord(uuid))
                           
                if valarm is not None:
                    records.append(
                           model.DisplayAlarmRecord(
                                             uuid,
                                             remDescription,
                                             trigger,
                                             remDuration,
                                             remRepeat
                                             ))
                else:
                    records.append(
                        model.DisplayAlarmRecord(
                            uuid,
                            None,
                            None,
                            None,
                            None))

                recordSets[uuid] = RecordSet(records)


            except vobject.base.VObjectError, e:
                icalendarLines = text.splitlines()
                logger.error("Exception when importing icalendar, first 300 lines: \n%s"
                             % "\n".join(icalendarLines[:300]))
                logger.exception("import failed to import one event with exception: %s" % str(e))
                if not silentFailure:
                    raise
Beispiel #13
0
def date_parse(view, s):
    """Parse using dateutil's parse, then convert to ICUtzinfo timezones."""
    return convertToICUtzinfo(view, dateutil_parse(s))