mail (IMAP) integration basically working

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3537 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2009-09-05 12:46:59 +00:00
parent 90b67d1265
commit a78ec01d5c
6 changed files with 107 additions and 36 deletions

View file

@ -38,7 +38,7 @@ of the 'extcollection' type. We use an adapter for providing the attributes
and methods of the external collect object.
>>> from loops.concept import Concept
>>> from loops.setup import addObject
>>> from loops.setup import addObject, addAndConfigureObject
>>> from loops.integrator.collection import ExternalCollectionAdapter
>>> tExternalCollection = concepts['extcollection']
>>> coll01 = addObject(concepts, Concept, 'coll01',
@ -141,10 +141,10 @@ Mail Collections
>>> tType = concepts['type']
>>> from loops.integrator.mail.interfaces import IMailCollection, IMailResource
>>> tMailCollection = addObject(concepts, Concept, 'mailcollection',
>>> tMailCollection = addAndConfigureObject(concepts, Concept, 'mailcollection',
... title=u'Mail Collection', conceptType=tType,
... typeInterface=IMailCollection)
>>> tMailResource = addObject(concepts, Concept, 'email',
>>> tMailResource = addAndConfigureObject(concepts, Concept, 'email',
... title=u'Mail Resource', conceptType=tType,
... typeInterface=IMailResource)
@ -157,17 +157,28 @@ Mail Collections
An external collection carries a set of attributes that control the access
to the external system:
>>> (aMailColl.providerName, aMailColl.baseAddress,
... aMailColl.address, aMailColl.pattern)
(u'imap', None, None, None)
>>> aMailColl.userName = u'jim'
>>> (aMailColl.providerName, aMailColl.baseAddress, aMailColl.address,
... aMailColl.pattern, aMailColl.userName)
(u'imap', None, None, None, u'jim')
>>> from loops.integrator.mail import testing
>>> from loops.integrator.mail.imap import IMAPCollectionProvider
>>> component.provideUtility(IMAPCollectionProvider(), name='imap')
>>> from loops.integrator.mail.resource import MailResource
>>> component.provideAdapter(MailResource, provides=IMailResource)
>>> aMailColl.update()
*** 1 ...
>>> aMail = adapted(mailColl.getResources()[0])
>>> aMail.date, aMail.sender, aMail.receiver, aMail.title
(datetime.datetime(...), 'ceo@cy55.de', 'ceo@example.org', 'Blogging from Munich')
>>> aMail.data
'<p><b>Blogging from ...</b><br />\n'
>>> aMail.externalAddress
u'imap://jim@merz12/20081208171745.e4ce2xm96cco80cg@cy55.de'
Uploading Resources with HTTP PUT Requests

View file

@ -84,7 +84,7 @@ class ExternalCollectionAdapter(AdapterBase):
# may be it would be better to return a file's hash
# for checking for changes...
oldFound.append(addr)
if mdate > self.lastUpdated:
if mdate and mdate > self.lastUpdated:
# force reindexing
notify(ObjectModifiedEvent(old[addr]))
else:

View file

@ -20,4 +20,15 @@
factory="loops.integrator.mail.imap.IMAPCollectionProvider"
name="imap" />
<zope:adapter factory="loops.integrator.mail.resource.MailResource"
provides="loops.integrator.mail.interfaces.IMailResource"
trusted="True" />
<zope:class class="loops.integrator.mail.resource.MailResource">
<require permission="zope.View"
interface="loops.integrator.mail.interfaces.IMailResource" />
<require permission="zope.ManageContent"
set_schema="loops.integrator.mail.interfaces.IMailResource" />
</zope:class>
</configure>

View file

@ -24,14 +24,18 @@ $Id$
"""
from datetime import datetime
import email
from logging import getLogger
import os
import time
from zope.app.container.interfaces import INameChooser
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.component import adapts
from zope.event import notify
from zope.lifecycleevent import ObjectModifiedEvent
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.event import notify
from zope.interface import implements
from zope.traversing.api import getName, getParent
@ -51,6 +55,10 @@ class IMAPCollectionProvider(object):
def collect(self, client):
client._collectedObjects = {}
hostName = client.baseAddress
if hostName in ('', None, 'localhost'):
hostName = os.uname()[1]
baseId = 'imap://%s@%s' % (client.userName, hostName)
imap = IMAP4(client.baseAddress)
imap.login(client.userName, client.password)
mailbox = 'INBOX'
@ -61,11 +69,13 @@ class IMAPCollectionProvider(object):
type, data = imap.search(None, 'ALL')
for num in data[0].split():
type, data = imap.fetch(num, '(RFC822)')
externalAddress = num
obj = data
mtime = datetime.today()
client._collectedObjects[externalAddress] = obj
yield externalAddress, mtime
raw_msg = data[0][1]
msg = email.message_from_string(raw_msg)
msgId = msg['Message-ID'].replace('<', '').replace('>', '')
externalAddress = '/'.join((baseId, msgId))
#mtime = datetime.today()
client._collectedObjects[externalAddress] = msg
yield externalAddress, None
def createExtFileObjects(self, client, addresses, extFileTypes=None):
loopsRoot = client.context.getLoopsRoot()
@ -73,16 +83,43 @@ class IMAPCollectionProvider(object):
contentType = 'text/plain'
resourceType = loopsRoot.getConceptManager()['email']
for addr in addresses:
print '***', addr, client._collectedObjects[addr]
return []
def _dummy(self):
name = self.generateName(container, addr)
title = self.generateTitle(addr)
obj = addAndConfigureObject(
container, Resource, name,
title=title,
resourceType=extFileType,
contentType=contentType,
)
msg = client._collectedObjects[addr]
title = msg['Subject']
sender = msg['From']
receiver = msg['To']
raw_date = msg['Date'].rsplit(' ', 1)[0]
fmt = '%a, %d %b %Y %H:%M:%S'
date = datetime(*(time.strptime(raw_date, fmt)[0:6]))
parts = self.getPayload(msg, {})
if 'html' in parts:
text = parts['html']
ct = 'text/html'
else:
text = parts.get('plain') or u'No message found.'
ct = 'text/plain'
obj = Resource(title)
name = INameChooser(container).chooseName(None, obj)
container[name] = obj
obj.resourceType = resourceType
adObj = adapted(obj)
adObj.externalAddress = addr
adObj.contentType = ct
adObj.sender = sender
adObj.receiver = receiver
adObj.date = date
adObj.data = text
notify(ObjectCreatedEvent(obj))
notify(ObjectModifiedEvent(obj))
yield obj
def getPayload(self, msg, parts):
if msg.is_multipart():
for part in msg.get_payload():
self.getPayload(part, parts)
else:
ct = msg['Content-Type']
if ct and ct.startswith('text/html'):
parts['html'] = msg.get_payload()
if ct and ct.startswith('text/plain'):
parts['plain'] = msg.get_payload()
return parts

View file

@ -41,14 +41,13 @@ class IMailCollection(IExternalCollection):
providerName = schema.TextLine(
title=_(u'Provider name'),
description=_(u'The name of a utility that provides the '
u'external objects; default is an IMAP '
u'collection provider.'),
u'external objects; default is an IMAP collection provider.'),
default=u'imap',
required=False)
baseAddress = schema.TextLine(
title=_(u'Host name'),
description=_(u'The host name part for accessing the '
u'external mail system, i.e. the mail server.'),
u'external mail system, i.e. the mail server.'),
required=True)
userName = schema.TextLine(
title=_(u'User name'),
@ -67,9 +66,22 @@ class IMailResource(ITextDocument):
"""
externalAddress = schema.BytesLine(
title=_(u'External Address'),
description=_(u'The full address of the email in the external '
u'email system.'),
default='',
missing_value='',
required=False)
title=_(u'External Address'),
description=_(u'The full address of the email in the external '
u'email system.'),
default='',
missing_value='',
required=False)
sender = schema.TextLine(
title=_(u'Sender'),
description=_(u'The email address of sender of the email.'),
required=False)
receiver = schema.TextLine(
title=_(u'Receiver'),
description=_(u'One or more email addresses of the receiver(s) of '
u'the email message.'),
required=False)
date = schema.Date(
title=_(u'Date'),
description=_(u'The date/time the email message was sent.'),
required=False,)

View file

@ -22,7 +22,7 @@ Fake access to system libraries for testing.
$Id$
"""
data = [('1 (RFC822 {7785}', 'Return-Path: <ceo-bounces@mailman.cy55.de>\r\nMessage-ID: <20081208171745.e4ce2xm96cco80cg@cy55.de>\r\nDate: Mon, 8 Dec 2008 17:17:45 +0100\r\nFrom: ceo@cy55.de\r\nTo: ceo@example.org\r\n\r\nThis message is in MIME format.\r\n\r\n--===============0371775080==\r\nContent-Type: multipart/alternative;\r\n\tboundary="=_gtkn1bgzg6g"\r\nContent-Transfer-Encoding: 7bit\r\n\r\nThis message is in MIME format.\r\n\r\n--=_gtkn1bgzg6g\r\nContent-Type: text/plain;\r\n\tcharset=ISO-8859-1;\r\n\tDelSp="Yes";\r\n\tformat="flowed"\r\nContent-Description: Plaintext Version of Message\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\n\r\n BLOGGING FROM ...\r\n.\r\n\r\n\r\n--=_gtkn1bgzg6g\r\nContent-Type: text/html;\r\n\tcharset=ISO-8859-1\r\nContent-Description: HTML Version of Message\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<p><b>Blogging from ...</b><br />\r\n\r\n'), ')']
data = [('1 (RFC822 {7785}', 'Return-Path: <ceo-bounces@mailman.cy55.de>\r\nMessage-ID: <20081208171745.e4ce2xm96cco80cg@cy55.de>\r\nDate: Mon, 8 Dec 2008 17:17:45 +0100\r\nFrom: ceo@cy55.de\r\nTo: ceo@example.org\r\nMIME-Version: 1.0\r\nSubject: Blogging from Munich\r\nContent-Type: multipart/mixed;\r\n\tboundary="===============0371775080=="\r\n\r\nThis message is in MIME format.\r\n\r\n--===============0371775080==\r\nContent-Type: multipart/alternative;\r\n\tboundary="=_gtkn1bgzg6g"\r\nContent-Transfer-Encoding: 7bit\r\n\r\nThis message is in MIME format.\r\n\r\n--=_gtkn1bgzg6g\r\nContent-Type: text/plain;\r\n\tcharset=ISO-8859-1;\r\n\tDelSp="Yes";\r\n\tformat="flowed"\r\nContent-Description: Plaintext Version of Message\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\n\r\n BLOGGING FROM ...\r\n.\r\n\r\n\r\n--=_gtkn1bgzg6g\r\nContent-Type: text/html;\r\n\tcharset=ISO-8859-1\r\nContent-Description: HTML Version of Message\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<p><b>Blogging from ...</b><br />\r\n\r\n'), ')']
class IMAP4(object):