From 5aee688a121dbcaf9d145b24439382d7f4fd070f Mon Sep 17 00:00:00 2001 From: scrat Date: Sun, 6 Apr 2008 17:28:30 +0000 Subject: [PATCH] check-in. At the moment problem with doctests for README.txt in cybertools/agent and cybertools/agent/crawl.Outlook.txt Problem Nr. 1 in /agent/README.txt: master.config Expected: controller.names = ['base.sample'] logger.name = 'default' logger.standard = 30 scheduler.name = 'sample' Got: logger.name = 'default' logger.standard = 30 scheduler.name = 'sample' Problem Nr. 2 in /agent/crawl/Outlook.txt: self.agent.setupAgents(self, [spec]) File "[..]\cybertools\agent\base\agent.py", line 85, in setupAgents agent.name = spec.name AttributeError: 'NoneType' object has no attribute 'name' git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2499 fd906abe-77d9-0310-91a1-e0d9ade77398 --- agent/README.txt | 2 +- agent/crawl/Outlook.txt | 51 +++++++++++ agent/crawl/base.py | 19 ++++ agent/crawl/mail.py | 16 +++- agent/crawl/outlook.py | 172 +++++++++++++++++++++++++++++++++++++ agent/crawl/outlookstub.py | 20 +++++ agent/tests.py | 1 + 7 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 agent/crawl/Outlook.txt create mode 100644 agent/crawl/outlook.py create mode 100644 agent/crawl/outlookstub.py diff --git a/agent/README.txt b/agent/README.txt index d1298de..e61fac2 100644 --- a/agent/README.txt +++ b/agent/README.txt @@ -1,4 +1,4 @@ -================================================ +================================================ Agents for Job Execution and Communication Tasks ================================================ diff --git a/agent/crawl/Outlook.txt b/agent/crawl/Outlook.txt new file mode 100644 index 0000000..82c0e4a --- /dev/null +++ b/agent/crawl/Outlook.txt @@ -0,0 +1,51 @@ +================================================ +Agents for Job Execution and Communication Tasks +================================================ + + + >>> from cybertools.agent.base.agent import Master + + >>> config = ''' + ... controller(names=['core.sample']) + ... scheduler(name='core') + ... logger(name='default', standard=30) + ... ''' + >>> master = Master(config) + >>> master.setup() + + +OutlookCrawler +============== + +The agent uses Twisted's cooperative multitasking model. + +OutlookCrawler is derived from MailCrawler. The OutlookCrawler returns a deferred +which itself holds a list of MailResource Objects. + +Returns a deferred that must be supplied with a callback method (and in +most cases also an errback method). + +The TestCase here is using subsidiary methods which replace calls to the "real Outlook +dlls". + + >>> controller = master.controllers[0] + >>> controller.createAgent('crawl.outlook', 'sample02') + +In the next step we request the start of a job, again via the controller. + + >>> controller.enterJob('outlook', 'sample02') + +The job is not executed immediately - we have to hand over control to +the twisted reactor first. + + >>> from cybertools.agent.crawl.outlook import OutlookCrawler + >>> from cybertools.agent.crawl.outlookstub import OutlookCrawlerStub + >>> OutlookCrawler.findOutlook = OutlookCrawlerStub.findOutlook + >>> OutlookCrawler = OutlookCrawlerStub + >>> from cybertools.agent.tests import tester + >>> tester.iterate() + Returning reference to Outlook Application + Retrieving Parameters + Crawling Folders + loading mails from folder + Job 00001 completed; result: []; diff --git a/agent/crawl/base.py b/agent/crawl/base.py index f1dcf7d..f0e75b6 100644 --- a/agent/crawl/base.py +++ b/agent/crawl/base.py @@ -27,6 +27,7 @@ from zope.interface import implements from cybertools.agent.base.agent import Master from cybertools.agent.core.agent import QueueableAgent from cybertools.agent.interfaces import ICrawler +from cybertools.agent.interfaces import IResource from cybertools.agent.components import agents from twisted.internet.defer import succeed @@ -50,5 +51,23 @@ class SampleCrawler(Crawler): d = succeed([]) return d + +class Resource(object): + + implements(IResource) + + data = None + path = "" + application = "" + metadata = None + + def __init__(self, data, path="", application="", metadata=None): + self.data = data + self.path = path + self.application = application + self.metadata = metadata + + + agents.register(SampleCrawler, Master, name='crawl.sample') diff --git a/agent/crawl/mail.py b/agent/crawl/mail.py index e1cf2cf..489a49d 100644 --- a/agent/crawl/mail.py +++ b/agent/crawl/mail.py @@ -24,7 +24,8 @@ $Id$ from zope.interface import implements -from cybertools.agent.agent import Agent +from cybertools.agent.base.agent import Agent, Master +from cybertools.agent.crawl.base import Resource from cybertools.agent.crawl.base import Crawler from cybertools.agent.components import agents from twisted.internet.defer import succeed @@ -41,6 +42,9 @@ class MailCrawler(Crawler): # d = self.crawlFolders() d = succeed([]) return d + + def fetchCriteria(self): + pass def crawlFolders(self): pass @@ -48,11 +52,15 @@ class MailCrawler(Crawler): def loadMailsFromFolder(self, folder): pass - def createResource(self, mail): - resource = mail - # do the real processing + def createResource(self, mail, path="", application="", metadata=None): + resource = MailResource(mail, path, application, metadata) self.result.append(resource) def login(self): pass + +class MailResource(Resource): + pass + +agents.register(MailCrawler, Master, name='crawl.mail') \ No newline at end of file diff --git a/agent/crawl/outlook.py b/agent/crawl/outlook.py new file mode 100644 index 0000000..e692c76 --- /dev/null +++ b/agent/crawl/outlook.py @@ -0,0 +1,172 @@ +# +# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Outlook Crawler Class. + +$Id: mail.py 2493 2008-04-03 11:17:37Z helmutm $ +""" + +import re + +from zope.interface import implements +from twisted.internet import defer +import win32com.client +import ctypes +import win32api, win32process, win32con + +from cybertools.agent.base.agent import Agent, Master +from cybertools.agent.crawl.mail import MailCrawler +from cybertools.agent.crawl.mail import MailResource +from cybertools.agent.components import agents + +# some constants +COMMASPACE = ', ' + +class OutlookCrawler(MailCrawler): + + keys = "" + inbox = "" + subfolders = "" + pattern = "" + + def collect(self, filter=None): + self.d = defer.Deferred() + self.oOutlookApp = None + if self.findOutlook(): + self.fetchCriteria() + self.d.addCallback(self.crawlFolders) + else: + pass + #self.d.addErrback([]) + return d + + def fetchCriteria(self): + criteria = self.params + self.keys = criteria.get('keys') + self.inbox = criteria.get('inbox') #boolean + self.subfolders = criteria.get('subfolders') #boolean + self.pattern = criteria.get('pattern') + if self.pattern != '': + self.pattern = re.compile(criteria.get('pattern') or '.*') + + def crawlFolders(self): + onMAPI = self.oOutlookApp.GetNamespace("MAPI") + ofInbox = \ + onMAPI.GetDefaultFolder(win32com.client.constants.olFolderInbox) + # fetch mails from inbox + if self.inbox: + self.loadMailsFromFolder(ofInbox) + # fetch mails of inbox subfolders + if self.subfolders and self.pattern is None: + lInboxSubfolders = getattr(ofInbox, 'Folders') + for of in range(lInboxSubfolders.__len__()): + # get a MAPI-subfolder object and load its emails + self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)) + elif self.subfolders and self.pattern: + lInboxSubfolders = getattr(ofInbox, 'Folders') + for of in range(lInboxSubfolders.__len__()): + # get specified MAPI-subfolder object and load its emails + if self.pattern.match(getattr(lInboxSubfolders.Item(of + 1), 'Name')): + self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)) + return self.result + + def loadMailsFromFolder(self, folder): + # get items of the folder + folderItems = getattr(folder, 'Items') + for item in range(len(folderItems)): + mail = folderItems.Item(item+1) + if mail.Class == win32com.client.constants.olMail: + if self.keys is None: + self.keys = [] + for key in mail._prop_map_get_: + if isinstance(getattr(mail, key), (int, str, unicode)): + self.keys.append(key) + record = {} + for key in self.keys: + record[key] = getattr(mail, key) + # Create the mime email object + msg = self.createEmailMime(record) + # Create a resource and append it to the result list + self.createResource(msg, folder, "Microsoft Office Outlook") + + def login(self): + pass + + def handleOutlookDialog(self): + """ + This function handles the outlook dialog, which appears if someone + tries to access to MS Outlook. + """ + hwnd = None + while True: + hwnd = ctypes.windll.user32.FindWindowExA(None, hwnd, None, None) + if hwnd == None: + break + else: + val = u"\0" * 1024 + ctypes.windll.user32.GetWindowTextW(hwnd, val, len(val)) + val = val.replace(u"\000", u"") + if val and repr(val) == "u'Microsoft Office Outlook'": + print repr(val) + # get the Main Control + form = findTopWindow(wantedText='Microsoft Office Outlook') + controls = findControls(form) + # get the check box + checkBox = findControl(form, wantedText='Zugriff') + setCheckBox(checkBox, 1) + # get the combo box + comboBox = findControl(form, wantedClass='ComboBox') + items = getComboboxItems(comboBox) + selectComboboxItem(comboBox, items[3])#'10 Minuten' + # finally get the button and click it + button = findControl(form, wantedText = 'Erteilen') + clickButton(button) + break + + def findOutlook(self): + outlookFound = 0 + try: + self.oOutlookApp = \ + win32com.client.gencache.EnsureDispatch("Outlook.Application") + outlookFound = 1 + except: + pass + return outlookFound + + def createEmailMime(self, emails): + # Create the container (outer) email message. + msg = MIMEMultipart() + msg['Subject'] = emails['Subject'].encode('utf-8') + if emails.has_key('SenderEmailAddress'): + sender = str(emails['SenderEmailAddress'].encode('utf-8')) + else: + sender = str(emails['SenderName'].encode('utf-8')) + msg['From'] = sender + recipients = [] + if emails.has_key('Recipients'): + for rec in range(emails['Recipients'].__len__()): + recipients.append(getattr(emails['Recipients'].Item(rec+1), 'Address')) + msg['To'] = COMMASPACE.join(recipients) + else: + recipients.append(emails['To']) + msg['To'] = COMMASPACE.join(recipients) + msg.preamble = emails['Body'].encode('utf-8') + return msg + +agents.register(OutlookCrawler, Master, name='crawl.outlook') diff --git a/agent/crawl/outlookstub.py b/agent/crawl/outlookstub.py new file mode 100644 index 0000000..8043cff --- /dev/null +++ b/agent/crawl/outlookstub.py @@ -0,0 +1,20 @@ +from cybertools.agent.crawl.mail import MailCrawler + +class OutlookCrawlerStub(MailCrawler): + + def __init__(self): + pass + + def findOutlook(self): + print "Returning reference to Outlook Application" + + def fetchCriteria(self): + print "Retrieving Parameters" + + def crawlFolders(self): + print "Crawling Folders" + self.loadMailsFromFolder() + + def loadMailsFromFolder(self): + print "loading mails from folder" + self.createResource("SampleMail", "OutlookStubFolder", "OutlookStub") \ No newline at end of file diff --git a/agent/tests.py b/agent/tests.py index 0e23dbf..3ff3d52 100755 --- a/agent/tests.py +++ b/agent/tests.py @@ -43,6 +43,7 @@ def test_suite(): unittest.makeSuite(Test), DocFileSuite('README.txt', optionflags=flags), DocFileSuite('crawl/README.txt', optionflags=flags), + DocFileSuite('crawl/Outlook.txt', optionflags=flags), )) return testSuite