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 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)