diff --git a/integrator/README.txt b/integrator/README.txt index 0630095..43af8fa 100644 --- a/integrator/README.txt +++ b/integrator/README.txt @@ -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 + '

Blogging from ...
\n' + >>> aMail.externalAddress + u'imap://jim@merz12/20081208171745.e4ce2xm96cco80cg@cy55.de' Uploading Resources with HTTP PUT Requests diff --git a/integrator/collection.py b/integrator/collection.py index 906bbcc..4203fe7 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -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: diff --git a/integrator/mail/configure.zcml b/integrator/mail/configure.zcml index 70b230a..2594c6d 100644 --- a/integrator/mail/configure.zcml +++ b/integrator/mail/configure.zcml @@ -20,4 +20,15 @@ factory="loops.integrator.mail.imap.IMAPCollectionProvider" name="imap" /> + + + + + + + diff --git a/integrator/mail/imap.py b/integrator/mail/imap.py index 9b154fa..94ec66a 100644 --- a/integrator/mail/imap.py +++ b/integrator/mail/imap.py @@ -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 diff --git a/integrator/mail/interfaces.py b/integrator/mail/interfaces.py index 387e2b0..7a1935b 100644 --- a/integrator/mail/interfaces.py +++ b/integrator/mail/interfaces.py @@ -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,) diff --git a/integrator/mail/testing.py b/integrator/mail/testing.py index 9c6955f..d2920a2 100644 --- a/integrator/mail/testing.py +++ b/integrator/mail/testing.py @@ -22,7 +22,7 @@ Fake access to system libraries for testing. $Id$ """ -data = [('1 (RFC822 {7785}', 'Return-Path: \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

Blogging from ...
\r\n\r\n'), ')'] +data = [('1 (RFC822 {7785}', 'Return-Path: \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

Blogging from ...
\r\n\r\n'), ')'] class IMAP4(object):