Example #1
0
def parseEventInfo(mailStamp):
    assert isinstance(mailStamp, MailStamp)

    if has_stamp(mailStamp.itsItem, EventStamp):
        # The message has been stamped as
        # an event which means its event info has
        # already been populated
        return

    eventStamp = EventStamp(mailStamp.itsItem)
    eventStamp.add()

    # This uses the default Chandler locale determined from
    # the OS or the command line flag --locale (-l)
    startTime, endTime, countFlag, typeFlag = \
                _parseEventInfoForLocale(mailStamp)

    #XXX the parsedatetime API does not always return the
    #    correct parsing results.
    #
    #    Further investigation needs to be done.
    #    What I would like to do is try in the user's current
    #    locale then try in English. But in my testing the
    #    parseText API returns a positive count flag when
    #    if the text contains date time info that does
    #    not match the passed locale. The value of the
    #    startTime and endTime wil be the current users
    #    localtime which is not correct.
    #
    #    I also see incorrect results when text contains
    #    a start and end date. As well as when the
    #    text contains localized times such as 4pm.
    #    In some instances it does correctly parse the time
    #    in others it does not.
    #
    #    The English parsing fallback is commented out till
    #    the above issues are rosolved.
    #
    #if countFlag == 0 and not getLocale().startswith(u"en"):
    #    # Lets try using English language date parsing rules
    #    # as a fallback.
    #    startTime, endTime, countFlag, typeFlag = \
    #               _parseEventInfoForLocale(messageObject, "en")

    if countFlag == 0:
        # No datetime info found in either the mail message subject
        # or the mail message body so do not set any event date time info.
        return

    setEventDateTime(mailStamp.itsItem, startTime,
                     endTime, typeFlag)
Example #2
0
def parseEventInfo(mailStamp):
    assert isinstance(mailStamp, MailStamp)

    if has_stamp(mailStamp.itsItem, EventStamp):
        # The message has been stamped as
        # an event which means its event info has
        # already been populated
        return

    eventStamp = EventStamp(mailStamp.itsItem)
    eventStamp.add()

    # This uses the default Chandler locale determined from
    # the OS or the command line flag --locale (-l)
    startTime, endTime, countFlag, typeFlag = \
                _parseEventInfoForLocale(mailStamp)

    #XXX the parsedatetime API does not always return the
    #    correct parsing results.
    #
    #    Further investigation needs to be done.
    #    What I would like to do is try in the user's current
    #    locale then try in English. But in my testing the
    #    parseText API returns a positive count flag when
    #    if the text contains date time info that does
    #    not match the passed locale. The value of the
    #    startTime and endTime wil be the current users
    #    localtime which is not correct.
    #
    #    I also see incorrect results when text contains
    #    a start and end date. As well as when the
    #    text contains localized times such as 4pm.
    #    In some instances it does correctly parse the time
    #    in others it does not.
    #
    #    The English parsing fallback is commented out till
    #    the above issues are rosolved.
    #
    #if countFlag == 0 and not getLocale().startswith(u"en"):
    #    # Lets try using English language date parsing rules
    #    # as a fallback.
    #    startTime, endTime, countFlag, typeFlag = \
    #               _parseEventInfoForLocale(messageObject, "en")

    if countFlag == 0:
        # No datetime info found in either the mail message subject
        # or the mail message body so do not set any event date time info.
        return

    setEventDateTime(mailStamp.itsItem, startTime, endTime, typeFlag)
Example #3
0
    def onSetContentsEvent(self, event):
        """
        Here would be a good place to make sure that items selected in
        the old contents are also selected in the new contents.

        """
        contents = event.arguments['item']
        # collectionList.first() is the currently selected collection in
        # the sidebar
        contentsCollection = contents.collectionList.first()
        self.setContentsOnBlock(contents, contentsCollection)

        # bug 5613, when a new collection is selected, items in overlaid
        # collections should be unselected
        selection = self.GetSelection()
        # Bug 5817, the iterable returned by iterSelection will complain if an
        # item is removed, so create a (seemingly useless) list before iterating
        for item in list(selection.iterSelection()):
            if (EventStamp(item).getMaster().itsItem
                    not in self.contentsCollection):
                selection.unselectItem(item)

        # Posting select items event will display the correct item in the detail view.
        self.postSelectItemsBroadcast()

        self.synchronizeWidget()
Example #4
0
    def OnSelectItem(self, item):
        """
        Called when an item is hit, to select the item.

        Subclasses can override to handle item selection.
        """
        selection = self.blockItem.GetSelection()
        # usually there's no need to call Refresh, postSelectItemsBroadcast will
        # cause notifications to force a redraw.  But if selection was empty
        # from clicking outside a lozenge, the selected item didn't
        # change in the rest of the system.  In that case nothing changes
        # in the repository, so do a redraw.
        shouldRefresh = selection.isSelectionEmpty()

        if item:
            selection.setSelectionToItem(item)
            shouldRefresh |= (getattr(item, 'inheritFrom', None) is not None
                              and EventStamp(item).modificationFor is None)
        else:
            selection.clearSelection()

        self.blockItem.postSelectItemsBroadcast()

        if shouldRefresh:
            self.Refresh()
Example #5
0
    def findClusters(self, toSend):
        """
        Return a list of tuples of (alias, deleteFlag) pairs,
        clustering recordsets that need to be serialized together
        (recurrence modifications and masters).  The first pair will
        be the master.

        For instance: [((master1, False), (mod1, False)), ((master2, False),)]

        """
        mastersChanged = set()
        mastersDeleted = set()

        view = self.itsView
        translator = self.translator(self.itsView)

        for alias, rs in toSend.iteritems():
            masterAlias, recurrenceID = splitUUID(view, alias)
            s = mastersChanged if (rs is not None
                                   or recurrenceID) else mastersDeleted
            s.add(masterAlias)

        mastersChanged = mastersChanged - mastersDeleted

        clusters = [((alias, True), ) for alias in mastersDeleted]
        for masterAlias in mastersChanged:
            cluster = [(masterAlias, False)]
            clusters.append(cluster)

            master = view.findUUID(masterAlias)
            for mod in getattr(EventStamp(master), 'modifications', []):
                cluster.append((translator.getAliasForItem(mod), False))
        return clusters
Example #6
0
    def AddItems(self, itemList):
        """
        Override this to add the dropped items to your widget.
        """
        if self.hoverDate is not None:
            for item in itemList:
                proxy = RecurrenceDialog.getProxy(u"ui", item, cancelCallback=self.wxSynchronizeWidget)
                event = EventStamp(proxy)
                if not has_stamp(proxy, EventStamp):
                    event.add()  # stamp as an event
                    event.anyTime = True
                oldTime = getattr(event, "startTime", self.hoverDate).timetz()
                event.startTime = datetime.combine(self.hoverDate, oldTime)
                proxy.setTriageStatus("auto")

        self.hoverDate = None
        self.Refresh()
Example #7
0
def addEventStamp(item, recur=False):
    es = EventStamp(item)
    es.add()
    es.summary = uw("Test Event Summary")

    tzinfo = item.itsView.tzinfo.floating

    # Choose random days, hours
    startDelta = timedelta(days=random.randint(0, 30),
                           hours=random.randint(0, 24))

    now = datetime.now(tzinfo)

    closeToNow = datetime(now.year,
                          now.month,
                          now.day,
                          now.hour,
                          int(now.minute / 30) * 30,
                          tzinfo=now.tzinfo)

    es.startTime = closeToNow + startDelta
    es.anyTime = True

    # Choose random minutes
    es.duration = timedelta(minutes=60)

    es.location = Calendar.Location.getLocation(view, uw("My House"))

    es.itsItem.importance = random.choice(pim.ImportanceEnum.values)

    es.itsItem.setTriageStatus(randomEnum(pim.TriageEnum))

    if recur:
        rule = RecurrenceRule(itsView=view)
        rule.freq = 'daily'
        rule.until = datetime(2008, 9, 14, 19, tzinfo=view.tzinfo.default)
        rule.untilIsDate = False

        ruleSet = RecurrenceRuleSet(itsView=view)
        ruleSet.addRule(rule)

        es.rruleset = ruleSet

    return es
Example #8
0
    def __init__(self, bounds, itemOrEvent):
        """
        @param bounds: the bounds of the item as drawn on the canvas.
        @type bounds: wx.Rect
        @param item: the item drawn on the canvas in these bounds
        @type itemOrEvent: C{Item} or C{EventStamp}
        """

        # @@@ scaffolding: resize bounds is the lower 5 pixels
        self._bounds = bounds

        if isinstance(itemOrEvent, EventStamp):
            self.event = itemOrEvent
        else:
            self.event = EventStamp(itemOrEvent)
Example #9
0
def addEventStamp(item, recur=False):
    es = EventStamp(item)
    es.add()
    es.summary = uw("Test Event Summary")

    tzinfo = view.tzinfo.floating

    # Choose random days, hours
    startDelta = timedelta(days=random.randint(0, 30),
                           hours=random.randint(0, 24))

    now = datetime.now(tzinfo)

    closeToNow = datetime(now.year, now.month, now.day, now.hour,
                          int(now.minute/30) * 30, tzinfo=now.tzinfo)

    es.startTime = closeToNow + startDelta
    es.anyTime = True

    # Choose random minutes
    es.duration = timedelta(minutes=60)

    es.location = Calendar.Location.getLocation(view, uw("My House"))

    es.itsItem.importance = random.choice(pim.ImportanceEnum.values)

    es.itsItem.setTriageStatus(randomEnum(pim.TriageEnum))

    if recur:
        rule = RecurrenceRule(itsView=view)
        rule.freq = 'daily'
        rule.until =  datetime(2008, 9, 14, 19, tzinfo=view.tzinfo.default)
        rule.untilIsDate = False

        ruleSet = RecurrenceRuleSet(itsView=view)
        ruleSet.addRule(rule)

        es.rruleset = ruleSet

    return es
    def AddItems(self, itemList):
        """
        Override this to add the dropped items to your widget.
        """
        if self.hoverDate is not None:
            for item in itemList:
                proxy = RecurrenceDialog.getProxy(
                    u'ui', item, cancelCallback=self.wxSynchronizeWidget)
                event = EventStamp(proxy)
                if not has_stamp(proxy, EventStamp):
                    event.add()  # stamp as an event
                    event.anyTime = True
                oldTime = getattr(event, 'startTime', self.hoverDate).timetz()
                event.startTime = datetime.combine(self.hoverDate, oldTime)
                proxy.setTriageStatus('auto')

        self.hoverDate = None
        self.Refresh()
Example #11
0
    def startTest(self):
        
        def mondayPlus(inc=0):
            """return a m/d/yy date string equal to this Monday plus inc days"""
            today = datetime.date.today()
            daysUntilMonday = today.weekday()
            if daysUntilMonday == 6: daysUntilMonday = -1 #sunday is special case
            monday = today - datetime.timedelta(days=daysUntilMonday)
            incDay =  monday + datetime.timedelta(days=inc)
            view = wx.GetApp().UIRepositoryView
            value = datetime.datetime.combine(incDay, datetime.time(0, tzinfo=view.tzinfo.default))
            dateStr = shortDateFormat.format(view, value)
            return dateStr
        
        # resize the Chandler window to (1024,720): this test sort of crumble if the window is too small
        frame = wx.GetApp().mainFrame
        frame.SetSize((1024,720))

        # switch to calendar view
        testView = QAUITestAppLib.UITestView(self.logger)
        testView.SwitchToCalView()       

        # make user collection, since only user
        # collections can be displayed as a calendar
        col = QAUITestAppLib.UITestItem("Collection", self.logger)

        evtDate = mondayPlus()
        evtSecondDate = mondayPlus(1)
        evtThirdDate = mondayPlus(2)
        evtRecurrenceEnd = mondayPlus(365)
        evtNextWeek = mondayPlus(7)
        
        # Make sure we're not showing timezones now (we'll put it back below)
        tzPrefs = schema.ns('osaf.pim', QAUITestAppLib.App_ns.itsView).TimezonePrefs
        oldTZPref = tzPrefs.showUI
        tzPrefs.showUI = False

        # Create a vanilla event; leave the timezone alone so we can make sure
        # it's floating.
        event = QAUITestAppLib.UITestItem("Event", self.logger)
        event.SetAttr(displayName=uw("Birthday Party"), 
                      startDate=evtDate, 
                      startTime="6:00 PM", 
                      location=uw("Club101"), 
                      status="FYI",
                      body=uw("This is a birthday party invitation"))
    
        # Check a few things: that those attributes got set right, plus
        # a few defaulty things worked (timezone, endtime)
        event.CheckDisplayedValues("Checking initial setup",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditAllDay=(True, False),
            EditCalendarStartDate=(True, evtDate),
            CalendarStartAtLabel=(True,),
            EditCalendarStartTime=(True, "6:00 PM"),
            EditCalendarEndDate=(True, evtDate),
            CalendarEndAtLabel=(True,),
            EditCalendarEndTime=(True, "7:00 PM"),
            CalendarLocation=(True, uw("Club101")),
            EditTransparency=(True, "FYI"),
            NotesBlock=(True, uw("This is a birthday party invitation")),
            EditTimeZone=(False, "None")) # Not visible with timezones off
    
        # Toggle allday, then make sure the right changes happened.
        event.SetAttr("Setting allDay", allDay=True)    
        event.CheckDisplayedValues("Checking allday",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditAllDay=(True, True),
            EditCalendarStartDate=(True, evtDate),
            CalendarStartAtLabel=(False,),
            EditCalendarStartTime=(False,),
            EditCalendarEndDate=(True, evtDate),
            CalendarEndAtLabel=(False,),
            EditCalendarEndTime=(False,),
            )
    
        # Turn on timezones, turn off alldayness, and make sure the popup appears
        tzPrefs.showUI = True
        event.SetAttr("Setting explicit timezone", 
                  allDay=False,
                  timeZone='America/Denver')
        event.CheckDisplayedValues("Changed Timezone",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditTimeZone=(True, 'America/Denver'),
            EditCalendarStartDate=(True, evtDate),
            EditCalendarEndDate=(True, evtDate),
            EditCalendarStartTime=(True,), # could check the time here if I knew the local tz
            EditCalendarEndTime=(True,),
            CalendarStartAtLabel=(True,),
            CalendarEndAtLabel=(True,)
            )
        
        # Make it recur
        event.SetAttr("Making it recur",
                      recurrence="Daily", 
                      recurrenceEnd=evtRecurrenceEnd)
        scripting.User.idle()
        event.CheckDisplayedValues("Checking recurrence",
            EditRecurrence=(True, "Daily"),
            EditRecurrenceEnd=(True, evtRecurrenceEnd))
    
        # Select the second occurrence and delete it
        masterEvent = EventStamp(event.item)
        secondEvent = QAUITestAppLib.UITestItem(
            masterEvent.getFirstOccurrence().getNextOccurrence(), self.logger)
        secondEvent.SelectItem()
        secondEvent.CheckDisplayedValues("Checking 2nd occurrence",
            EditCalendarStartDate=(True, evtSecondDate),
            )
        secondEvent.MoveToTrash()
        scripting.User.idle()
    
        # Answer the recurrence question with "just this item"
        self.logger.startAction('Test recurrence dialog')
        recurrenceDialog = wx.FindWindowByName(u'RecurrenceDialog')
        if recurrenceDialog is None:
            self.logger.endAction(False, "Didn't see the recurrence dialog when deleting a recurrence instance")
        else:
            scripting.User.emulate_click(recurrenceDialog.thisButton)
            scripting.User.idle()
            self.logger.endAction(True)
            
        # Make sure the new second occurrence starts on the right date
        thirdEvent = QAUITestAppLib.UITestItem(
            masterEvent.getFirstOccurrence().getNextOccurrence(), self.logger)
        thirdEvent.SelectItem()
        thirdEvent.CheckDisplayedValues("After deleting second occurrence",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditCalendarStartDate=(True, evtThirdDate),
            )

        # Create an event in a future week
        futureEvent = QAUITestAppLib.UITestItem("Event", self.logger)
        futureEvent.SetAttr(displayName=uw("Future Weekly"), 
                            startDate=evtNextWeek, 
                            startTime="6:00 PM", 
                            recurrence="Weekly",
                            body=uw("This is an event in the future"))
        futureEvent.CheckDisplayedValues("Checking future recurring event",
            HeadlineBlock=(True, uw("Future Weekly")),
            EditAllDay=(True, False),
            EditCalendarStartDate=(True, evtNextWeek),
            CalendarStartAtLabel=(True,),
            EditCalendarStartTime=(True, "6:00 PM"),
            EditCalendarEndDate=(True, evtNextWeek),
            CalendarEndAtLabel=(True,),
            EditCalendarEndTime=(True, "7:00 PM"),
            NotesBlock=(True, uw("This is an event in the future")))

        #leave Chandler with timezones turned off
        tzPrefs.showUI = False
Example #12
0
def kindToMessageObject(mailStamp):
    """
    This method converts an item stamped as MailStamp to an email message
    string
    a Chandler C{MailMessage} object

    @param mailMessage: A Chandler C{MailMessage}
    @type mailMessage: C{MailMessage}

    @return: C{Message.Message}
    """

    view = mailStamp.itsItem.itsView

    mailStampOccurrence, mailStampMaster = getRecurrenceMailStamps(mailStamp)

    isEvent = has_stamp(mailStampOccurrence, EventStamp)
    isTask = has_stamp(mailStampOccurrence, TaskStamp)

    messageObject = Message.Message()

    # Create a messageId if none exists
    mId = getattr(mailStampMaster, "messageId", None)

    if not mId:
        mId = createMessageID()

    populateHeader(messageObject, 'Message-ID', mId)
    populateEmailAddresses(mailStampMaster, messageObject)
    populateStaticHeaders(messageObject)

    if hasattr(mailStampMaster, "dateSentString"):
        date = mailStampMaster.dateSentString
    else:
        date = datetimeToRFC2822Date(datetime.now(view.tzinfo.default))

    messageObject["Date"] = date

    inReplyTo = getattr(mailStampMaster, "inReplyTo", None)

    subject = mailStampOccurrence.subject

    if subject is not None:
        # Fixes bug 10254 where the title of a Item
        # that contained a new line was breaking the
        # the rfc2822 formatting of the outgoing message.
        subject = subject.replace("\n", "")

    if inReplyTo:
        messageObject["In-Reply-To"] = inReplyTo

    if mailStampMaster.referencesMID:
        messageObject["References"] = " ".join(mailStampMaster.referencesMID)

    populateHeader(messageObject, 'Subject', subject, encode=True)

    try:
        payload = getMessageBody(mailStampOccurrence)
    except AttributeError:
        payload = u""

    if not payload:
        # bug 12262, Outlook doesn't like multipart/alternative if there's
        # no payload, so add a few carriage returns to empty bodies
        payload += "\r\n\r\n"

    if isTask or isEvent and payload and \
        not payload.endswith(u"\r\n\r\n"):
        # Chandler outgoing Tasks and Events contain
        # an ics attachment.
        # Many mail readers add attachment icons
        # at the end of the message body.
        # This can be distracting and visually
        # ugly. Appending two line breaks to the
        # payload provides better alignment in
        # mail readers such as Apple Mail and
        # Thunderbird.
        payload += u"\r\n\r\n"

    messageObject.set_type("multipart/mixed")

    # Create a multipart/alernative MIME Part
    # that will contain the Chandler eimml and
    # the body of the message as alternative
    # parts. Doing this prevents users from seeing
    # the Chandler eimml which is machine readable
    # xml code and is not displayable to the user.
    alternative = MIMEMultipart("alternative")

    # Serialize and attach the eimml can raise ConflictsPending
    eimml = outbound(getPeers(mailStampMaster), mailStampMaster.itsItem,
                     OUTBOUND_FILTERS)

    eimmlPayload = MIMEBase64Encode(eimml, 'text', 'eimml')

    # Since alternative parts are in order from least
    # renderable to most renderable add the eimml payload
    # first.
    alternative.attach(eimmlPayload)

    # Attach the body text
    mt = MIMEBase64Encode(payload.encode('utf-8'))

    # Add the email body text to the alternative part
    alternative.attach(mt)

    # Add the alternative part to the mail multipart/mixed
    # main content type.
    messageObject.attach(alternative)

    #XXX There is no attachement support in 1.0
    #hasAttachments = mailStamp.getNumberOfAttachments() > 0

    if isEvent or isTask:
        # Format this message as an ICalendar object
        from osaf.sharing import (serialize, VObjectSerializer,
                                  SharingTranslator, remindersFilter)
        items = [mailStampMaster.itsItem]
        for mod in EventStamp(mailStampMaster).modifications or []:
            if not checkTriageOnly(mod):
                items.append(mod)

        calendar = serialize(mailStamp.itsItem.itsView,
                             items,
                             SharingTranslator,
                             VObjectSerializer,
                             filter=remindersFilter)

        # don't use method REQUEST because it will cause Apple iCal to treat
        # the ics attachment as iMIP
        calendar.add('method').value = "PUBLISH"
        ics = calendar.serialize()  # returns a UTF-8 encoded str

        # Attach the ICalendar object
        icsPayload = MIMEBase64Encode(ics,
                                      'text',
                                      'calendar',
                                      method='PUBLISH')

        # L10N: The filename of Events and Tasks emailed from Chandler
        fname = Header.Header(_(u"ChandlerItem.ics")).encode()
        icsPayload.add_header("Content-Disposition",
                              "attachment",
                              filename=fname)
        messageObject.attach(icsPayload)

    #XXX: There is no attachment support in 1.0 via
    # the MailStamp.mimeContent. Commenting out this code
    # for now.
    #
    #if hasAttachments:
    #    attachments = mailStamp.getAttachments()
    #
    #    for attachment in attachments:
    #        if has_stamp(attachment, MailStamp):
    #            # The attachment is another MailMessage
    #            try:
    #                rfc2822 = binaryToData(MailStamp(attachment).rfc2822Message)
    #            except AttributeError:
    #                rfc2822 = kindToMessageText(attachment, False)
    #
    #            message = email.message_from_string(rfc2822)
    #            rfc2822Payload = MIMEMessage(message)
    #            messageObject.attach(rfc2822Payload)
    #
    #        else:
    #            if isinstance(attachment, MIMEText) and \
    #                attachment.mimeType == u"text/calendar":
    #                icsPayload = MIMENonMultipart('text', 'calendar', \
    #                                    method='REQUEST', _charset="utf-8")
    #
    #                fname = Header.Header(attachment.filename).encode()
    #                icsPayload.add_header("Content-Disposition", "attachment", filename=fname)
    #                icsPayload.set_payload(attachment.data.encode('utf-8'))
    #                messageObject.attach(icsPayload)

    return messageObject
Example #13
0
        # and use it. (If it was an existing event, we'll reuse it.)
        item = items[0]
        # use the master, if a modification happens to be the first item
        item = getattr(item, 'inheritFrom', item)

        if item.displayName and len(item.displayName.strip()):
            # The displayName will contain the ics summary
            icsSummary = item.displayName

        if item.body and len(item.displayName.strip()):
            # The body will contain the ics description
            icsDesc = item.body

        if not has_stamp(item, MailStamp):
            if has_stamp(item, EventStamp):
                EventStamp(item).addStampToAll(MailStamp)
            else:
                ms = MailStamp(item)
                ms.add()

        mailStamp = MailStamp(item)
        mailStamp.fromEIMML = False

        return (mailStamp, icsDesc, icsSummary)

    return None


def buildBody(bodyBuffer):
    if len(bodyBuffer.get('plain')):
        body = removeCarriageReturn(u"\n".join(bodyBuffer.get('plain')))
    def onMarkAsReadEvent(self, event):
        selectedItems = self.__getSelectedItems(event)
        haveUnread = any(item for item in selectedItems if not item.read)

        for item in selectedItems:
            EventStamp(item).getMaster().itsItem.read = haveUnread
Example #15
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
Example #16
0
def itemsToVObject(view, items, cal=None, filters=None):
    """
    Iterate through items, add to cal, create a new vcalendar if needed.

    Consider only master events (then serialize all modifications).  For now,
    set all timezones to Pacific.

    """

    if filters is None:
        filters = () # we want filters to be iterable
    
    def makeDateTimeValue(dt, asDate=False):
        if asDate:
            return dt.date()
        elif dt.tzinfo == view.tzinfo.floating:
            return dt.replace(tzinfo=None)
        else:
            return dt

    
    def populateCommon(comp, item):
        """
        Populate the given vevent or vtodo vobject with values for
        attributes common to Events or Tasks).
        """
        
        if getattr(item, Note.icalUID.name, None) is None:
            item.icalUID = unicode(item.itsUUID)
        comp.add('uid').value = item.icalUID

        # displayName --> SUMMARY
        try:
            summary = item.displayName
        except AttributeError:
            pass
        else:
            comp.add('summary').value = summary
            
        # body --> DESCRIPTION
        try:
            description = item.body
        except AttributeError:
            pass
        else:
            if description:
                comp.add('description').value = description
                
        # userReminder --> VALARM
        if Remindable.reminders.name not in filters:
            firstReminder = item.getUserReminder()
            if firstReminder is not None:
                if firstReminder.absoluteTime is not None:
                    value = firstReminder.absoluteTime
                else:
                    # @@@ For now, all relative reminders are relative to starttime
                    assert firstReminder.relativeTo == EventStamp.effectiveStartTime.name
                    value = firstReminder.delta
                comp.add('valarm').add('trigger').value = value
                
    def populateCustom(comp, item):
        # custom properties
        for name, value in item.icalendarProperties.iteritems():
            prop = comp.add(name)

            # for unrecognized properties, import stores strings, not
            # native types like datetimes.  So value should just be a
            # string, not a more complicated python data structure.  Don't
            # try to transform the value when serializing
            prop.isNative = False
            
            # encoding escapes characters like backslash and comma and
            # combines list values into a single string.  This was already
            # done when the icalendar was imported, so don't escape again
            prop.encoded = True
            
            prop.value = value
                
        for name, paramstring in item.icalendarParameters.iteritems():
            paramdict = comp.contents[name][0].params
            for paramlist in vobject.base.parseParams(paramstring):
                # parseParams gives a list of lists of parameters, with the
                # first element of each list being the name of the
                # parameter, followed by the parameter values, if any
                paramname = paramlist[0].upper()
                if paramname.lower() in parametersUnderstood:
                    # parameters understood by Chandler shouldn't be stored
                    # in icalendarParameters, but changes to which
                    # parameters Chandler understands can lead to spurious
                    # parameters, ignore them
                    continue
                paramvalues = paramdict.setdefault(paramname, [])
                paramvalues.extend(paramlist[1:])
        
        

    
    def populateEvent(comp, event):
        """Populate the given vobject vevent with data from event."""
        
        populateCommon(comp, event.itsItem)
        
        try:
            dtstartLine = comp.add('dtstart')
            
            # allDay-ness overrides anyTime-ness
            if event.anyTime and not event.allDay:
                dtstartLine.x_osaf_anytime_param = 'TRUE'
                
            dtstartLine.value = makeDateTimeValue(event.startTime,
                                    event.anyTime or event.allDay)

        except AttributeError:
            comp.dtstart = [] # delete the dtstart that was added
        
        try:
            if not (event.duration == datetime.timedelta(0) or (
                    (event.anyTime or event.allDay) and 
                    event.duration <= oneDay)):
                dtendLine = comp.add('dtend')
                #convert Chandler's notion of allDay duration to iCalendar's
                if event.allDay:
                    dtendLine.value = event.endTime.date() + oneDay
                else:
                    if event.anyTime:
                        dtendLine.x_osaf_anytime_param = 'TRUE'

                    # anyTime should be exported as allDay for non-Chandler apps
                    dtendLine.value = makeDateTimeValue(event.endTime,
                                                        event.anyTime)

        except AttributeError:
            comp.dtend = [] # delete the dtend that was added
            

        if EventStamp.transparency.name not in filters:
            try:
                status = event.transparency.upper()
                # anytime events should be interpreted as not taking up time,
                # but all-day shouldn't
                if status == 'FYI' or (not event.allDay and event.anyTime):
                    status = 'CANCELLED'
                comp.add('status').value = status
            except AttributeError:
                pass

        try:
            comp.add('location').value = event.location.displayName
        except AttributeError:
            pass
        
        view = event.itsItem.itsView
        timestamp = datetime.datetime.utcnow()
        comp.add('dtstamp').value = timestamp.replace(tzinfo=view.tzinfo.UTC)

        if event.modificationFor is not None:
            recurrenceid = comp.add('recurrence-id')
            masterEvent = event.getMaster()
            allDay = masterEvent.allDay or masterEvent.anyTime
            
            recurrenceid.value = makeDateTimeValue(event.recurrenceID, allDay)
        
        # logic for serializing rrules needs to move to vobject
        try: # hack, create RRULE line last, because it means running transformFromNative
            if event.getMaster() == event and event.rruleset is not None:
                # False because we don't want to ignore isCount for export
                # True because we don't want to use view.tzinfo.floating
                cal.vevent_list[-1].rruleset = event.createDateUtilFromRule(False, True, False)
        except AttributeError:
            logger.error('Failed to export RRULE for %s' % event.itsItem.itsUUID)
        # end of populateEvent function
        
        populateCustom(comp, event.itsItem)


    def populateModifications(event, cal):
        for modification in itertools.imap(EventStamp, event.modifications):
            for attr, val in modification.itsItem.iterModifiedAttributes():
                if attr in attributesUsedWhenExporting and attr not in filters:
                    populateEvent(cal.add('vevent'), modification)
                    break
        #end helper functions
        
    def populateTask(comp, task):
        """Populate the given vobject vtodo with data from task."""
        populateCommon(comp, task.itsItem)

        # @@@ [grant] Once we start writing out Event+Tasks as
        # VTODO, write out DUE (or maybe DTSTART) here.

        if Note._triageStatus.name not in filters:
            triageStatus = task.itsItem._triageStatus
            
            # VTODO STATUS mapping:
            # ---------------------
            #
            #  [ICalendar]            [Triage Enum]
            #  <no value>/IN-PROCESS    now  (needsReply=False)
            #  NEEDS-ACTION             now  (needsReply=True)
            #  COMPLETED                done
            #  CANCELLED                later
            
            if triageStatus == TriageEnum.now:
                if task.itsItem.needsReply:
                    comp.add('status').value = 'needs-action'
                else:
                    comp.add('status').value = 'in-process'
            elif triageStatus == TriageEnum.later:
                comp.add('status').value = 'cancelled'
            else:
                comp.add('status').value = 'completed'
                
        populateCustom(comp, task.itsItem)

    if cal is None:
        cal = vobject.iCalendar()
    for item in items: # main loop
        try:
            # ignore any events that aren't masters
            #
            # Note: [grant]
            # At the moment, we allow Event-ness to take precedence over
            # Task-ness. So, we serialize Event+Task objects as VEVENTs.
            # Part of the reason for this is that recurring VTODOs aren't
            # so well-supported by other iCalendar clients. For RFC 2445
            # issues with VTODO+RRULE, see e.g.
            # <http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001134.html>
            if has_stamp(item, EventStamp):
                event = EventStamp(item)
                if event.getMaster() == event:
                  populateEvent(cal.add('vevent'), event)
                populateModifications(event, cal)
            elif has_stamp(item, TaskStamp):
                  populateTask(cal.add('vtodo'), TaskStamp(item))
        except:
            logger.exception("Exception while exporting %s" % item)
            continue

    return cal
Example #17
0
    def startTest(self):
        def mondayPlus(inc=0):
            """return a m/d/yy date string equal to this Monday plus inc days"""
            today = datetime.date.today()
            daysUntilMonday = today.weekday()
            if daysUntilMonday == 6:
                daysUntilMonday = -1  #sunday is special case
            monday = today - datetime.timedelta(days=daysUntilMonday)
            incDay = monday + datetime.timedelta(days=inc)
            view = wx.GetApp().UIRepositoryView
            value = datetime.datetime.combine(
                incDay, datetime.time(0, tzinfo=view.tzinfo.default))
            dateStr = shortDateFormat.format(view, value)
            return dateStr

        # resize the Chandler window to (1024,720): this test sort of crumble if the window is too small
        frame = wx.GetApp().mainFrame
        frame.SetSize((1024, 720))

        # switch to calendar view
        testView = QAUITestAppLib.UITestView(self.logger)
        testView.SwitchToCalView()

        # make user collection, since only user
        # collections can be displayed as a calendar
        col = QAUITestAppLib.UITestItem("Collection", self.logger)

        evtDate = mondayPlus()
        evtSecondDate = mondayPlus(1)
        evtThirdDate = mondayPlus(2)
        evtRecurrenceEnd = mondayPlus(365)
        evtNextWeek = mondayPlus(7)

        # Make sure we're not showing timezones now (we'll put it back below)
        tzPrefs = schema.ns('osaf.pim',
                            QAUITestAppLib.App_ns.itsView).TimezonePrefs
        oldTZPref = tzPrefs.showUI
        tzPrefs.showUI = False

        # Create a vanilla event; leave the timezone alone so we can make sure
        # it's floating.
        event = QAUITestAppLib.UITestItem("Event", self.logger)
        event.SetAttr(displayName=uw("Birthday Party"),
                      startDate=evtDate,
                      startTime="6:00 PM",
                      location=uw("Club101"),
                      status="FYI",
                      body=uw("This is a birthday party invitation"))

        # Check a few things: that those attributes got set right, plus
        # a few defaulty things worked (timezone, endtime)
        event.CheckDisplayedValues(
            "Checking initial setup",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditAllDay=(True, False),
            EditCalendarStartDate=(True, evtDate),
            CalendarStartAtLabel=(True, ),
            EditCalendarStartTime=(True, "6:00 PM"),
            EditCalendarEndDate=(True, evtDate),
            CalendarEndAtLabel=(True, ),
            EditCalendarEndTime=(True, "7:00 PM"),
            CalendarLocation=(True, uw("Club101")),
            EditTransparency=(True, "FYI"),
            NotesBlock=(True, uw("This is a birthday party invitation")),
            EditTimeZone=(False, "None"))  # Not visible with timezones off

        # Toggle allday, then make sure the right changes happened.
        event.SetAttr("Setting allDay", allDay=True)
        event.CheckDisplayedValues(
            "Checking allday",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditAllDay=(True, True),
            EditCalendarStartDate=(True, evtDate),
            CalendarStartAtLabel=(False, ),
            EditCalendarStartTime=(False, ),
            EditCalendarEndDate=(True, evtDate),
            CalendarEndAtLabel=(False, ),
            EditCalendarEndTime=(False, ),
        )

        # Turn on timezones, turn off alldayness, and make sure the popup appears
        tzPrefs.showUI = True
        event.SetAttr("Setting explicit timezone",
                      allDay=False,
                      timeZone='America/Denver')
        event.CheckDisplayedValues(
            "Changed Timezone",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditTimeZone=(True, 'America/Denver'),
            EditCalendarStartDate=(True, evtDate),
            EditCalendarEndDate=(True, evtDate),
            EditCalendarStartTime=(
                True, ),  # could check the time here if I knew the local tz
            EditCalendarEndTime=(True, ),
            CalendarStartAtLabel=(True, ),
            CalendarEndAtLabel=(True, ))

        # Make it recur
        event.SetAttr("Making it recur",
                      recurrence="Daily",
                      recurrenceEnd=evtRecurrenceEnd)
        scripting.User.idle()
        event.CheckDisplayedValues("Checking recurrence",
                                   EditRecurrence=(True, "Daily"),
                                   EditRecurrenceEnd=(True, evtRecurrenceEnd))

        # Select the second occurrence and delete it
        masterEvent = EventStamp(event.item)
        secondEvent = QAUITestAppLib.UITestItem(
            masterEvent.getFirstOccurrence().getNextOccurrence(), self.logger)
        secondEvent.SelectItem()
        secondEvent.CheckDisplayedValues(
            "Checking 2nd occurrence",
            EditCalendarStartDate=(True, evtSecondDate),
        )
        secondEvent.MoveToTrash()
        scripting.User.idle()

        # Answer the recurrence question with "just this item"
        self.logger.startAction('Test recurrence dialog')
        recurrenceDialog = wx.FindWindowByName(u'RecurrenceDialog')
        if recurrenceDialog is None:
            self.logger.endAction(
                False,
                "Didn't see the recurrence dialog when deleting a recurrence instance"
            )
        else:
            scripting.User.emulate_click(recurrenceDialog.thisButton)
            scripting.User.idle()
            self.logger.endAction(True)

        # Make sure the new second occurrence starts on the right date
        thirdEvent = QAUITestAppLib.UITestItem(
            masterEvent.getFirstOccurrence().getNextOccurrence(), self.logger)
        thirdEvent.SelectItem()
        thirdEvent.CheckDisplayedValues(
            "After deleting second occurrence",
            HeadlineBlock=(True, uw("Birthday Party")),
            EditCalendarStartDate=(True, evtThirdDate),
        )

        # Create an event in a future week
        futureEvent = QAUITestAppLib.UITestItem("Event", self.logger)
        futureEvent.SetAttr(displayName=uw("Future Weekly"),
                            startDate=evtNextWeek,
                            startTime="6:00 PM",
                            recurrence="Weekly",
                            body=uw("This is an event in the future"))
        futureEvent.CheckDisplayedValues(
            "Checking future recurring event",
            HeadlineBlock=(True, uw("Future Weekly")),
            EditAllDay=(True, False),
            EditCalendarStartDate=(True, evtNextWeek),
            CalendarStartAtLabel=(True, ),
            EditCalendarStartTime=(True, "6:00 PM"),
            EditCalendarEndDate=(True, evtNextWeek),
            CalendarEndAtLabel=(True, ),
            EditCalendarEndTime=(True, "7:00 PM"),
            NotesBlock=(True, uw("This is an event in the future")))

        #leave Chandler with timezones turned off
        tzPrefs.showUI = False
Example #18
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
Example #19
0
def itemsToVObject(view, items, cal=None, filters=None):
    """
    Iterate through items, add to cal, create a new vcalendar if needed.

    Consider only master events (then serialize all modifications).  For now,
    set all timezones to Pacific.

    """

    if filters is None:
        filters = ()  # we want filters to be iterable

    def makeDateTimeValue(dt, asDate=False):
        if asDate:
            return dt.date()
        elif dt.tzinfo == view.tzinfo.floating:
            return dt.replace(tzinfo=None)
        else:
            return dt

    def populateCommon(comp, item):
        """
        Populate the given vevent or vtodo vobject with values for
        attributes common to Events or Tasks).
        """

        if getattr(item, Note.icalUID.name, None) is None:
            item.icalUID = unicode(item.itsUUID)
        comp.add("uid").value = item.icalUID

        # displayName --> SUMMARY
        try:
            summary = item.displayName
        except AttributeError:
            pass
        else:
            comp.add("summary").value = summary

        # body --> DESCRIPTION
        try:
            description = item.body
        except AttributeError:
            pass
        else:
            if description:
                comp.add("description").value = description

        # userReminder --> VALARM
        if Remindable.reminders.name not in filters:
            firstReminder = item.getUserReminder()
            if firstReminder is not None:
                if firstReminder.absoluteTime is not None:
                    value = firstReminder.absoluteTime
                else:
                    # @@@ For now, all relative reminders are relative to starttime
                    assert firstReminder.relativeTo == EventStamp.effectiveStartTime.name
                    value = firstReminder.delta
                comp.add("valarm").add("trigger").value = value

    def populateCustom(comp, item):
        # custom properties
        for name, value in item.icalendarProperties.iteritems():
            prop = comp.add(name)

            # for unrecognized properties, import stores strings, not
            # native types like datetimes.  So value should just be a
            # string, not a more complicated python data structure.  Don't
            # try to transform the value when serializing
            prop.isNative = False

            # encoding escapes characters like backslash and comma and
            # combines list values into a single string.  This was already
            # done when the icalendar was imported, so don't escape again
            prop.encoded = True

            prop.value = value

        for name, paramstring in item.icalendarParameters.iteritems():
            paramdict = comp.contents[name][0].params
            for paramlist in vobject.base.parseParams(paramstring):
                # parseParams gives a list of lists of parameters, with the
                # first element of each list being the name of the
                # parameter, followed by the parameter values, if any
                paramname = paramlist[0].upper()
                if paramname.lower() in parametersUnderstood:
                    # parameters understood by Chandler shouldn't be stored
                    # in icalendarParameters, but changes to which
                    # parameters Chandler understands can lead to spurious
                    # parameters, ignore them
                    continue
                paramvalues = paramdict.setdefault(paramname, [])
                paramvalues.extend(paramlist[1:])

    def populateEvent(comp, event):
        """Populate the given vobject vevent with data from event."""

        populateCommon(comp, event.itsItem)

        try:
            dtstartLine = comp.add("dtstart")

            # allDay-ness overrides anyTime-ness
            if event.anyTime and not event.allDay:
                dtstartLine.x_osaf_anytime_param = "TRUE"

            dtstartLine.value = makeDateTimeValue(event.startTime, event.anyTime or event.allDay)

        except AttributeError:
            comp.dtstart = []  # delete the dtstart that was added

        try:
            if not (
                event.duration == datetime.timedelta(0)
                or ((event.anyTime or event.allDay) and event.duration <= oneDay)
            ):
                dtendLine = comp.add("dtend")
                # convert Chandler's notion of allDay duration to iCalendar's
                if event.allDay:
                    dtendLine.value = event.endTime.date() + oneDay
                else:
                    if event.anyTime:
                        dtendLine.x_osaf_anytime_param = "TRUE"

                    # anyTime should be exported as allDay for non-Chandler apps
                    dtendLine.value = makeDateTimeValue(event.endTime, event.anyTime)

        except AttributeError:
            comp.dtend = []  # delete the dtend that was added

        if EventStamp.transparency.name not in filters:
            try:
                status = event.transparency.upper()
                # anytime events should be interpreted as not taking up time,
                # but all-day shouldn't
                if status == "FYI" or (not event.allDay and event.anyTime):
                    status = "CANCELLED"
                comp.add("status").value = status
            except AttributeError:
                pass

        try:
            comp.add("location").value = event.location.displayName
        except AttributeError:
            pass

        view = event.itsItem.itsView
        timestamp = datetime.datetime.utcnow()
        comp.add("dtstamp").value = timestamp.replace(tzinfo=view.tzinfo.UTC)

        if event.modificationFor is not None:
            recurrenceid = comp.add("recurrence-id")
            masterEvent = event.getMaster()
            allDay = masterEvent.allDay or masterEvent.anyTime

            recurrenceid.value = makeDateTimeValue(event.recurrenceID, allDay)

        # logic for serializing rrules needs to move to vobject
        try:  # hack, create RRULE line last, because it means running transformFromNative
            if event.getMaster() == event and event.rruleset is not None:
                # False because we don't want to ignore isCount for export
                # True because we don't want to use view.tzinfo.floating
                cal.vevent_list[-1].rruleset = event.createDateUtilFromRule(False, True, False)
        except AttributeError:
            logger.error("Failed to export RRULE for %s" % event.itsItem.itsUUID)
        # end of populateEvent function

        populateCustom(comp, event.itsItem)

    def populateModifications(event, cal):
        for modification in itertools.imap(EventStamp, event.modifications):
            for attr, val in modification.itsItem.iterModifiedAttributes():
                if attr in attributesUsedWhenExporting and attr not in filters:
                    populateEvent(cal.add("vevent"), modification)
                    break
        # end helper functions

    def populateTask(comp, task):
        """Populate the given vobject vtodo with data from task."""
        populateCommon(comp, task.itsItem)

        # @@@ [grant] Once we start writing out Event+Tasks as
        # VTODO, write out DUE (or maybe DTSTART) here.

        if Note._triageStatus.name not in filters:
            triageStatus = task.itsItem._triageStatus

            # VTODO STATUS mapping:
            # ---------------------
            #
            #  [ICalendar]            [Triage Enum]
            #  <no value>/IN-PROCESS    now  (needsReply=False)
            #  NEEDS-ACTION             now  (needsReply=True)
            #  COMPLETED                done
            #  CANCELLED                later

            if triageStatus == TriageEnum.now:
                if task.itsItem.needsReply:
                    comp.add("status").value = "needs-action"
                else:
                    comp.add("status").value = "in-process"
            elif triageStatus == TriageEnum.later:
                comp.add("status").value = "cancelled"
            else:
                comp.add("status").value = "completed"

        populateCustom(comp, task.itsItem)

    if cal is None:
        cal = vobject.iCalendar()
    for item in items:  # main loop
        try:
            # ignore any events that aren't masters
            #
            # Note: [grant]
            # At the moment, we allow Event-ness to take precedence over
            # Task-ness. So, we serialize Event+Task objects as VEVENTs.
            # Part of the reason for this is that recurring VTODOs aren't
            # so well-supported by other iCalendar clients. For RFC 2445
            # issues with VTODO+RRULE, see e.g.
            # <http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001134.html>
            if has_stamp(item, EventStamp):
                event = EventStamp(item)
                if event.getMaster() == event:
                    populateEvent(cal.add("vevent"), event)
                populateModifications(event, cal)
            elif has_stamp(item, TaskStamp):
                populateTask(cal.add("vtodo"), TaskStamp(item))
        except:
            logger.exception("Exception while exporting %s" % item)
            continue

    return cal