Beispiel #1
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
Beispiel #2
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
    def RoundTrip(self):

        view0 = self.views[0]
        view1 = self.views[1]

        item0 = view0.findUUID(self.uuid)

        pje = schema.Item(itsView=view0, itsName="pje")
        morgen = schema.Item(itsView=view1, itsName="morgen")

        item0.setTriageStatus(pim.TriageEnum.later)

        pim.EventStamp(item0).add()
        event = pim.EventStamp(item0)
        event.anyTime = False

        # morgen sends to pje
        self.assert_(not pim.has_stamp(item0, sharing.SharedItem))
        view0.commit()
        text = sharing.outbound([pje], item0)
        view0.commit()
        self.assert_(pim.has_stamp(item0, sharing.SharedItem))

        # pje receives from morgen
        self.assert_(view1.findUUID(self.uuid) is None)
        view1.commit()
        item1 = sharing.inbound(morgen, text)
        view1.commit()
        self.assert_(pim.has_stamp(item1, sharing.SharedItem))
        self.assertEqual(item1.displayName, "test displayName")
        self.assertEqual(item1.body, "test body")
        self.assertEqual(item1.triageStatus, pim.TriageEnum.later)

        shared0 = sharing.SharedItem(item0)
        shared1 = sharing.SharedItem(item1)

        self.assert_(not list(shared0.getConflicts()))

        # verify inbound filters (URIs defined in model.py)
        filter = sharing.getFilter(['cid:[email protected]'])
        item0.setTriageStatus(pim.TriageEnum.now)
        view0.commit()
        text = sharing.outbound([pje], item0)
        view0.commit()
        view1.commit()
        sharing.inbound(morgen, text, filter=filter)
        view1.commit()
        # triageStatus is unchanged because we filtered it on inbound
        self.assertEqual(item1.triageStatus, pim.TriageEnum.later)
        self.assert_(not shared1.conflictingStates)

        item0.setTriageStatus(pim.TriageEnum.done)
        view0.commit()
        text = sharing.outbound([pje], item0, filter=filter)
        view0.commit()
        view1.commit()
        sharing.inbound(morgen, text)
        view1.commit()
        # triageStatus is unchanged because we filtered it on outbound
        self.assertEqual(item1.triageStatus, pim.TriageEnum.later)
        self.assert_(not shared1.conflictingStates)

        item0.setTriageStatus(pim.TriageEnum.now)
        view0.commit()
        text = sharing.outbound([pje], item0)
        view0.commit()
        view1.commit()
        sharing.inbound(morgen, text)
        view1.commit()
        # with no filtering, triageStatus is now changed
        self.assertEqual(item1.triageStatus, pim.TriageEnum.now)



        # conflict
        item0.displayName = "changed by morgen"
        item1.displayName = "changed by pje"
        view0.commit()
        text = sharing.outbound([pje], item0)
        view0.commit()
        view1.commit()
        sharing.inbound(morgen, text)
        view1.commit()
        conflicts = list(shared1.getConflicts())
        self.assert_(conflicts)


        # try sending when there are pending conflicts
        try:
            sharing.outbound([morgen], item1)
        except sharing.ConflictsPending:
            pass # This is what we're expecting
        else:
            raise Exception("We were expecting a ConflictsPending exception")



        # removal
        view0.commit()
        text = sharing.outboundDeletion(view0, [pje], self.uuid)
        view0.commit()
        # allowDeletion flag False
        view1.commit()
        sharing.inbound(morgen, text, allowDeletion=False)
        view1.commit() # to give a chance for a deleted item to go away
        self.assert_(view1.findUUID(self.uuid) is not None)
        # allowDeletion flag True
        sharing.inbound(morgen, text, allowDeletion=True)
        view1.commit() # to give a chance for a deleted item to go away
        self.assert_(view1.findUUID(self.uuid) is None)

        # adding item back
        text = sharing.outbound([pje], item0)
        item1 = sharing.inbound(morgen, text)
        shared1 = sharing.SharedItem(item1)
        self.assert_(view1.findUUID(self.uuid) is item1)

        # overlapping but identical modifications results in no conflicts
        item0.displayName = "changed"
        item1.displayName = "changed"
        view0.commit()
        text = sharing.outbound([pje], item0)
        view0.commit()
        view1.commit()
        sharing.inbound(morgen, text)
        view1.commit()
        # Examine the conflicts and ensure the 'title' field isn't conflicting
        self.assert_(not shared1.conflictingStates)


        # Verify that an out of sequence update is ignored
        before = shared1.getPeerState(morgen, create=False)
        beforeAgreed = before.agreed # copy the old agreed recordset
        # item0.displayName is "changed"
        view0.itsVersion = 2 # Back in time
        # Now item0.displayName is "test displayName"
        text = sharing.outbound([pje], item0)

        try:
            sharing.inbound(morgen, text, debug=False)
        except sharing.OutOfSequence:
            pass # Thisis what we're expecting
        else:
            raise Exception("We were expecting an OutOfSequence exception")

        after = shared1.getPeerState(morgen, create=False)
        self.assertEqual(beforeAgreed, after.agreed)