From de60a56f59415780b116b4645d63ddd6f31ae18b Mon Sep 17 00:00:00 2001 From: helmutm Date: Fri, 30 May 2008 05:25:12 +0000 Subject: [PATCH] cleared loops.agent package - is now cybertools.agent git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2647 fd906abe-77d9-0310-91a1-e0d9ade77398 --- agent/README.txt | 369 ------ agent/__init__.py | 4 - agent/config.py | 138 -- agent/core.py | 92 -- agent/crawl/__init__.py | 4 - agent/crawl/base.py | 59 - agent/crawl/filesystem.py | 84 -- agent/crawl/filesystem.txt | 42 - agent/crawl/outlook.py | 277 ---- agent/crawl/watsup/AppControls.py | 152 --- agent/crawl/watsup/OrderedDict.py | 61 - agent/crawl/watsup/PControl.py | 389 ------ agent/crawl/watsup/__init__.py | 0 agent/crawl/watsup/docs/WatsupIE methods.doc | Bin 42496 -> 0 bytes agent/crawl/watsup/docs/html/code/example1.py | 10 - agent/crawl/watsup/docs/html/code/example2.py | 27 - agent/crawl/watsup/docs/html/code/example3.py | 17 - agent/crawl/watsup/docs/html/code/example4.py | 51 - .../crawl/watsup/docs/html/code/example4a.py | 3 - .../crawl/watsup/docs/html/code/example4b.py | 3 - .../watsup/docs/html/images/ShowWindows1.jpg | Bin 28696 -> 0 bytes .../watsup/docs/html/images/ShowWindows2.jpg | Bin 60292 -> 0 bytes .../watsup/docs/html/images/framework1.jpg | Bin 46739 -> 0 bytes .../watsup/docs/html/images/framework2.jpg | Bin 57321 -> 0 bytes .../watsup/docs/html/images/framework3.jpg | Bin 127162 -> 0 bytes agent/crawl/watsup/docs/html/intro.html | 343 ----- agent/crawl/watsup/docs/images/tizmoi.jpg | Bin 6827 -> 0 bytes agent/crawl/watsup/docs/tac.css | 7 - agent/crawl/watsup/launcher.py | 116 -- agent/crawl/watsup/performance.py | 181 --- agent/crawl/watsup/timedunittest.py | 279 ---- agent/crawl/watsup/tools/ShowWindows.bat | 1 - agent/crawl/watsup/tools/__init__.py | 0 agent/crawl/watsup/tools/buildSuite.py | 96 -- agent/crawl/watsup/tools/findAppControls.py | 42 - agent/crawl/watsup/tools/fmShowWindows.py | 127 -- agent/crawl/watsup/tools/runTests.ini | 4 - agent/crawl/watsup/tools/showWindows.py | 34 - agent/crawl/watsup/tools/watsup_framework.bat | 1 - agent/crawl/watsup/tools/watsup_framework.py | 653 --------- agent/crawl/watsup/tools/wxShowWindows.py | 29 - agent/crawl/watsup/utils.py | 37 - agent/crawl/watsup/wie.py | 45 - agent/crawl/watsup/winGuiAuto.py | 1169 ----------------- agent/interfaces.py | 264 ---- agent/log.py | 76 -- agent/loops.tac | 22 - agent/schedule.py | 118 -- agent/testing/__init__.py | 4 - agent/testing/client.py | 53 - agent/testing/crawl.py | 52 - agent/testing/data/file1.txt | 1 - agent/testing/data/subdir/file2.txt | 1 - agent/testing/server.py | 63 - agent/testing/testing.cfg | 6 - agent/testing/transport.py | 47 - agent/tests.py | 75 -- agent/transport/__init__.py | 4 - agent/transport/base.py | 135 -- agent/transport/httpput.txt | 15 - agent/ui/StartUpAgentUI.tac.py | 26 - agent/ui/__init__.py | 4 - agent/ui/joblist.txt | 4 - agent/ui/resources/base.css | 99 -- agent/ui/resources/custom.css | 28 - agent/ui/resources/favicon.png | Bin 580 -> 0 bytes agent/ui/resources/loops.css | 138 -- agent/ui/resources/loops.js | 126 -- agent/ui/resources/loops_logo.png | Bin 1774 -> 0 bytes agent/ui/resources/loops_logo2.png | Bin 13258 -> 0 bytes agent/ui/resources/print.css | 13 - agent/ui/resources/zope3_tablelayout.css | 662 ---------- agent/ui/templates/agent.html | 70 - agent/ui/templates/filecrawl.html | 228 ---- agent/ui/templates/footer.html | 7 - agent/ui/templates/header.html | 22 - agent/ui/templates/jobdetail.html | 79 -- agent/ui/templates/joblisting.html | 89 -- agent/ui/templates/mail_detailed.html | 87 -- agent/ui/templates/mailcrawl.html | 197 --- agent/ui/templates/navigation.html | 41 - agent/ui/templates/ressourceview.html | 79 -- agent/ui/templates/top.html | 5 - agent/ui/twistd.py | 21 - agent/ui/usermode.ini | 1 - agent/ui/web.py | 1059 --------------- 86 files changed, 8967 deletions(-) delete mode 100644 agent/README.txt delete mode 100644 agent/__init__.py delete mode 100644 agent/config.py delete mode 100644 agent/core.py delete mode 100644 agent/crawl/__init__.py delete mode 100644 agent/crawl/base.py delete mode 100644 agent/crawl/filesystem.py delete mode 100644 agent/crawl/filesystem.txt delete mode 100644 agent/crawl/outlook.py delete mode 100644 agent/crawl/watsup/AppControls.py delete mode 100644 agent/crawl/watsup/OrderedDict.py delete mode 100644 agent/crawl/watsup/PControl.py delete mode 100644 agent/crawl/watsup/__init__.py delete mode 100644 agent/crawl/watsup/docs/WatsupIE methods.doc delete mode 100644 agent/crawl/watsup/docs/html/code/example1.py delete mode 100644 agent/crawl/watsup/docs/html/code/example2.py delete mode 100644 agent/crawl/watsup/docs/html/code/example3.py delete mode 100644 agent/crawl/watsup/docs/html/code/example4.py delete mode 100644 agent/crawl/watsup/docs/html/code/example4a.py delete mode 100644 agent/crawl/watsup/docs/html/code/example4b.py delete mode 100644 agent/crawl/watsup/docs/html/images/ShowWindows1.jpg delete mode 100644 agent/crawl/watsup/docs/html/images/ShowWindows2.jpg delete mode 100644 agent/crawl/watsup/docs/html/images/framework1.jpg delete mode 100644 agent/crawl/watsup/docs/html/images/framework2.jpg delete mode 100644 agent/crawl/watsup/docs/html/images/framework3.jpg delete mode 100644 agent/crawl/watsup/docs/html/intro.html delete mode 100644 agent/crawl/watsup/docs/images/tizmoi.jpg delete mode 100644 agent/crawl/watsup/docs/tac.css delete mode 100644 agent/crawl/watsup/launcher.py delete mode 100644 agent/crawl/watsup/performance.py delete mode 100644 agent/crawl/watsup/timedunittest.py delete mode 100644 agent/crawl/watsup/tools/ShowWindows.bat delete mode 100644 agent/crawl/watsup/tools/__init__.py delete mode 100644 agent/crawl/watsup/tools/buildSuite.py delete mode 100644 agent/crawl/watsup/tools/findAppControls.py delete mode 100644 agent/crawl/watsup/tools/fmShowWindows.py delete mode 100644 agent/crawl/watsup/tools/runTests.ini delete mode 100644 agent/crawl/watsup/tools/showWindows.py delete mode 100644 agent/crawl/watsup/tools/watsup_framework.bat delete mode 100644 agent/crawl/watsup/tools/watsup_framework.py delete mode 100644 agent/crawl/watsup/tools/wxShowWindows.py delete mode 100644 agent/crawl/watsup/utils.py delete mode 100644 agent/crawl/watsup/wie.py delete mode 100644 agent/crawl/watsup/winGuiAuto.py delete mode 100644 agent/interfaces.py delete mode 100644 agent/log.py delete mode 100644 agent/loops.tac delete mode 100644 agent/schedule.py delete mode 100644 agent/testing/__init__.py delete mode 100644 agent/testing/client.py delete mode 100644 agent/testing/crawl.py delete mode 100644 agent/testing/data/file1.txt delete mode 100644 agent/testing/data/subdir/file2.txt delete mode 100644 agent/testing/server.py delete mode 100644 agent/testing/testing.cfg delete mode 100644 agent/testing/transport.py delete mode 100755 agent/tests.py delete mode 100644 agent/transport/__init__.py delete mode 100644 agent/transport/base.py delete mode 100644 agent/transport/httpput.txt delete mode 100644 agent/ui/StartUpAgentUI.tac.py delete mode 100644 agent/ui/__init__.py delete mode 100644 agent/ui/joblist.txt delete mode 100644 agent/ui/resources/base.css delete mode 100644 agent/ui/resources/custom.css delete mode 100644 agent/ui/resources/favicon.png delete mode 100644 agent/ui/resources/loops.css delete mode 100644 agent/ui/resources/loops.js delete mode 100644 agent/ui/resources/loops_logo.png delete mode 100644 agent/ui/resources/loops_logo2.png delete mode 100644 agent/ui/resources/print.css delete mode 100644 agent/ui/resources/zope3_tablelayout.css delete mode 100644 agent/ui/templates/agent.html delete mode 100644 agent/ui/templates/filecrawl.html delete mode 100644 agent/ui/templates/footer.html delete mode 100644 agent/ui/templates/header.html delete mode 100644 agent/ui/templates/jobdetail.html delete mode 100644 agent/ui/templates/joblisting.html delete mode 100644 agent/ui/templates/mail_detailed.html delete mode 100644 agent/ui/templates/mailcrawl.html delete mode 100644 agent/ui/templates/navigation.html delete mode 100644 agent/ui/templates/ressourceview.html delete mode 100644 agent/ui/templates/top.html delete mode 100644 agent/ui/twistd.py delete mode 100644 agent/ui/usermode.ini delete mode 100644 agent/ui/web.py diff --git a/agent/README.txt b/agent/README.txt deleted file mode 100644 index 4c01b87..0000000 --- a/agent/README.txt +++ /dev/null @@ -1,369 +0,0 @@ -=============================================================== -loops - Linked Objects for Organization and Processing Services -=============================================================== - -loops agents - running on client systems and other services, -collecting informations and transferring them to the loops server. - - ($Id$) - -This package does not depend on zope or the other loops packages -but represents a standalone application. - -But we need a reactor for working with Twisted; in order not to block -testing when running the reactor we use reactor.iterate() calls -wrapped in a ``tester`` object. - - >>> from loops.agent.tests import tester - - -Basic Implementation, Agent Core -================================ - -The agent uses Twisted's cooperative multitasking model. - -This means that all calls to services (like crawler, transporter, ...) -return a deferred that must be supplied with a callback method (and in -most cases also an errback method). - - >>> from loops.agent import core - >>> agent = core.Agent() - - -Configuration Management -======================== - -Functionality - -- Storage of configuration parameters -- Interface to the browser-based user interface that allows the - editing of configuration parameters - -All configuration parameters are always accessible via the ``config`` -attribute of the agent object. - - >>> config = agent.config - -This already provides all needed sections (transport, crawl, ui), so -we can directly put information into these sections by loading a -string with the corresponding assignment. - - >>> config.load('transport.serverURL = "http://loops.cy55.de"') - >>> config.transport.serverURL - 'http://loops.cy55.de' - -This setting may also contain indexed access; thus we can model -configuration parameters with multiple instances (like crawling -jobs). - - >>> config.load(''' - ... crawl[0].type = "filesystem" - ... crawl[0].directory = "documents/projects" - ... ''') - >>> config.crawl[0].type - 'filesystem' - >>> config.crawl[0].directory - 'documents/projects' - -Subsections are created automatically when they are first accessed. - - >>> config.load('ui.web.port = 8081') - >>> config.ui.web.port - 8081 - -The ``setdefault()`` method allows to retrieve a value and set -it with a default if not found, in one statement. - - >>> config.ui.web.setdefault('port', 8080) - 8081 - >>> config.transport.setdefault('userName', 'loops') - 'loops' - - >>> sorted(config.transport.items()) - [('__name__', 'transport'), ('serverURL', 'http://loops.cy55.de'), ('userName', 'loops')] - -We can output a configuration in a form that is ready for loading -just by converting it to a string representation. - - >>> print config - crawl[0].directory = 'documents/projects' - crawl[0].type = 'filesystem' - transport.serverURL = 'http://loops.cy55.de' - transport.userName = 'loops' - ui.web.port = 8081 - -The configuration may also be saved to a file - -for testing purposes let's use the loops.agent package directory -for storage; normally it would be stored in the users home directory. - - >>> import os - >>> os.environ['HOME'] = os.path.dirname(core.__file__) - - >>> config.save() - - >>> fn = config.getDefaultConfigFile() - >>> fn - '....loops.agent.cfg' - - >>> print open(fn).read() - crawl[0].directory = 'documents/projects' - crawl[0].type = 'filesystem' - transport.serverURL = 'http://loops.cy55.de' - transport.userName = 'loops' - ui.web.port = 8081 - -The simplified syntax ---------------------- - - >>> config.load(''' - ... ui( - ... web( - ... port=11080, - ... )) - ... crawl[1]( - ... type='outlook', - ... folder='inbox', - ... ) - ... ''') - >>> print config.ui.web.port - 11080 - -Cleaning up ------------ - - >>> os.unlink(fn) - - -Scheduling -========== - -Configuration (per job) - -- schedule, repeating pattern, conditions -- following job(s), e.g. to start a transfer immediately after a crawl - -How does this work? -------------------- - - >>> from time import time - - >>> from loops.agent.schedule import Job - >>> class TestJob(Job): - ... def execute(self): - ... d = super(TestJob, self).execute() - ... print 'executing' - ... return d - - >>> scheduler = agent.scheduler - -The ``schedule()`` method accepts the start time as a second argument, -if not present use the current time, i.e. start the job immediately. - - >>> startTime = scheduler.schedule(TestJob()) - - >>> tester.iterate() - executing - -We can set up a more realistic example using the dummy crawler and transporter -classes from the testing package. - - >>> from loops.agent.testing import crawl - >>> from loops.agent.testing import transport - - >>> crawlJob = crawl.CrawlingJob() - >>> transporter = transport.Transporter(agent) - >>> transportJob = transporter.createJob() - >>> crawlJob.successors.append(transportJob) - >>> startTime = scheduler.schedule(crawlJob) - -The Job class offers two callback hooks: ``whenStarted`` and ``whenFinished``. -Use this for getting notified about the starting and finishing of a job. - - >>> def finishedCB(job, result): - ... print 'Crawling finished, result:', result - >>> crawlJob.whenFinished = finishedCB - -Now let the reactor run... - - >>> tester.iterate() - Crawling finished, result: [] - Transferring: Dummy resource data for testing purposes. - -Using configuration with scheduling ------------------------------------ - -Let's start with a fresh agent, directly supplying the configuration -(just for testing). - - >>> config = ''' - ... crawl[0].type = 'dummy' - ... crawl[0].directory = '~/documents' - ... crawl[0].pattern = '*.doc' - ... crawl[0].starttime = %s - ... crawl[0].transport = 'dummy' - ... crawl[0].repeat = 0 - ... transport.serverURL = 'http://loops.cy55.de' - ... ''' % int(time()) - - >>> agent = core.Agent(config) - -We also register our dummy crawling job and transporter classes as -we can not perform real crawling and transfers when testing. - - >>> agent.crawlTypes = dict(dummy=crawl.CrawlingJob) - >>> agent.transportTypes = dict(dummy=transport.Transporter) - - >>> agent.scheduleJobsFromConfig() - - >>> tester.iterate() - Transferring: Dummy resource data for testing purposes. - - -Crawling -======== - -General -------- - -Functionality - -- search for new or changed resources according to the search and - filter criteria -- keep a record of resources transferred already in order to avoid - duplicate transfers (?) - -Configuration (per crawl job) - -- predefined metadata - -Local File System ------------------ - -Configuration (per crawl job) - -- directories to search -- filter criteria, e.g. file type - -Metadata sources - -- path, filename - -Implementation and documentation: see loops/agent/crawl/filesystem.py -and .../filesystem.txt. - -E-Mail-Clients --------------- - -Configuration (per crawl job) - -- folders to search -- filter criteria (e.g. sender, receiver, subject patterns) - -Metadata sources - -- folder names (path) -- header fields (sender, receiver, subject, ...) - -Special handling - -- HTML vs. plain text content: if a mail contains both HTML and plain - text parts the transfer may be limited to one of these parts (configuration - setting) -- attachments may be ignored (configuration setting; useful when attachments - are copied to the local filesystem and transferred from there anyways) - - -Transport -========= - -Configuration - -- ``transport.serverURL``: URL of the target loops site, e.g. - "http://z3.loops.cy55.de/bwp/d5" -- ``transport.userName``, ``transport.password`` for logging in to loops -- ``transport.machineName: name under which the client computer is - known to the loops server -- ``transport.method``, e.g. "PUT" - -The following information is intended for the default transfer -protocol/method HTTP PUT but probably also pertains to other protocols -like e.g. FTP. - -Format/Information structure ----------------------------- - -- Metadata URL (for storing or accessing metadata sets - optional, see below): - ``$loopsSiteURL/resource_meta/$machine_name/$user/$app/$path.xml`` -- Resource URL (for storing or accessing the real resources): - ``$loopsSiteURL/resource_data/$machine_name//$user/$app/$path`` -- ``$app`` names the type of application providing the resource, e.g. - "filesystem" or "mail" -- ``$path`` represents the full path, possibly with drive specification in front - (for filesystem resources on Windows), with special characters URL-escaped - -Note that the URL uniquely identifies the resource on the local computer, -so a resource transferred with the exact location (path and filename) -on the local computer as a resource transferred previously will overwrite -the old version, so that the classification of the resource within loops -won't get lost. (This is of no relevance to emails.) - -Metadata sets are XML files with metadata for the associated resource. -Usually a metadata set has the extension ".xml"; if the extension is ".zip" -the metadata file is a compressed file that will be expanded on the -server. - -Data files may also be compressed in which case there must be a corresponding -entry in the associated metadata set. - - -Logging -======= - -Configuration - -- log format(s) -- log file(s) (or other forms of persistence) - -Example -------- - -We set the logging configuration to log level 20 (INFO) using the -standard log handler that prints to ``sys.stdout``. - - >>> agent.config.logging.standard = 20 - >>> logger = agent.logger - >>> logger.setup() - -The we can log an event providing a dictionary with the data to be logged. - - >>> logger.log(dict(object='job', event='start')) - 20... event:start object:job - -We can also look at the logging records collected in the logger. - - >>> len(logger) - 1 - - >>> print logger[-1] - 20... event:start object:job - - -Software Loader -=============== - -Configuration (general) - -- source list: URL(s) of site(s) providing updated or additional packages - -Configuration (per install/update job) - -- command: install, update, remove -- package names - - -Browser-based User Interface -============================ - -The user interface is provided via a browser-based application -based on Twisted and Nevow. - diff --git a/agent/__init__.py b/agent/__init__.py deleted file mode 100644 index 4bc90fb..0000000 --- a/agent/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -$Id$ -""" - diff --git a/agent/config.py b/agent/config.py deleted file mode 100644 index ced33a8..0000000 --- a/agent/config.py +++ /dev/null @@ -1,138 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Management of agent configuration. - -$Id$ -""" - -import os -from zope.interface import implements -from loops.agent.interfaces import IConfigurator - - -_not_found = object() - - -class Configurator(dict): - - implements(IConfigurator) - - def __init__(self, *sections, **kw): - for s in sections: - setattr(self, s, ConfigSection(s)) - self.filename = kw.get('filename') - - def __getitem__(self, key): - return getattr(self, key, ConfigSection(key)) - - def load(self, p=None, filename=None): - if p is None: - fn = self.getConfigFile(filename) - if fn is not None: - f = open(fn, 'r') - p = f.read() - f.close() - if p is None: - return - exec p in self - - def save(self, filename=None): - fn = self.getConfigFile(filename) - if fn is None: - fn = self.getDefaultConfigFile() - if fn is not None: - f = open(fn, 'w') - f.write(repr(self)) - f.close() - - def __repr__(self): - result = [] - for name, value in self.__dict__.items(): - if isinstance(value, ConfigSection): - value.collect(name, result) - return '\n'.join(sorted(result)) - - def getConfigFile(self, filename=None): - if filename is not None: - self.filename = filename - if self.filename is None: - fn = self.getDefaultConfigFile() - if os.path.isfile(fn): - self.filename = fn - return self.filename - - def getDefaultConfigFile(self): - return os.path.join(os.path.expanduser('~'), '.loops.agent.cfg') - - -class ConfigSection(list): - - __name__ = '???' - - def __init__(self, name=None): - if name is not None: - self.__name__ = name - - def __getattr__(self, attr): - value = ConfigSection(attr) - setattr(self, attr, value) - return value - - def __getitem__(self, idx): - while idx >= len(self): - self.append(ConfigSection()) - return list.__getitem__(self, idx) - - def setdefault(self, attr, value): - if attr not in self.__dict__: - setattr(self, attr, value) - return value - return getattr(self, attr) - - def items(self): - for name, value in self.__dict__.items(): - if isinstance(value, (str, int)): - yield name, value - - def __call__(self, *args, **kw): - for s in args: - if isinstance(s, ConfigSection): - # should we update an existing entry? - #old = getattr(self, s.__name__, None) - #if old is not None: # this would have to be done recursively - # old.__dict__.update(s.__dict__) - # for elem in s: - # old.append(elem) - #else: - # or just keep the new one? - setattr(self, s.__name__, s) - for k, v in kw.items(): - setattr(self, k, v) - return self - - def collect(self, ident, result): - for idx, element in enumerate(self): - element.collect('%s[%i]' % (ident, idx), result) - for name, value in self.__dict__.items(): - if isinstance(value, ConfigSection): - value.collect('%s.%s' % (ident, name), result) - elif name != '__name__' and isinstance(value, (str, int)): - result.append('%s.%s = %s' % (ident, name, repr(value))) - diff --git a/agent/core.py b/agent/core.py deleted file mode 100644 index 296bc14..0000000 --- a/agent/core.py +++ /dev/null @@ -1,92 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -The real agent stuff. - -$Id$ -""" - -from time import time -import tempfile -from zope.interface import implements -from loops.agent.interfaces import IAgent -from loops.agent.config import Configurator -from loops.agent.crawl import filesystem -from loops.agent.log import Logger -from loops.agent.schedule import Scheduler, Stopper -from loops.agent.transport import base - - -crawlTypes = dict( - filesystem=filesystem.CrawlingJob, -) - -transportTypes = dict( - httpput=base.Transporter, -) - - -class Agent(object): - - implements(IAgent) - - crawlTypes = crawlTypes - transportTypes = transportTypes - - def __init__(self, conf=None): - config = self.config = Configurator('ui', 'crawl', 'transport', 'logging') - config.load(conf) - self.scheduler = Scheduler(self) - self.stopper = Stopper() - self.stopper.scheduler = self.scheduler - self.logger = Logger(self) - self.tempdir = tempfile.mkdtemp(prefix='loops_') - - def scheduleJobsFromConfig(self, stop=False): - config = self.config - scheduler = self.scheduler - lastJob = None - for idx, info in enumerate(config.crawl): - crawlType = info.type - factory = self.crawlTypes.get(crawlType) - if factory is not None: - job = lastJob = factory() - job.params = dict((name, value) - for name, value in info.items() - if name not in job.baseProperties) - transportType = info.transport or 'httpput' - factory = self.transportTypes.get(transportType) - if factory is not None: - params = dict(config.transport.items()) - transporter = factory(self, **params) - # TODO: configure transporter or - better - - # set up transporter(s) just once - job.successors.append(transporter.createJob()) - job.repeat = info.repeat or 0 - self.scheduler.schedule(job, info.starttime or int(time())) - # TODO: remove job from config - # TODO: put repeating info in config - # TODO: remember last run for repeating job - if stop: - if lastJob is not None: - lastTrJob = lastJob.successors[-1] - lastTrJob.successors.append(self.stopper) - else: - self.scheduler.schedule(self.stopper) - diff --git a/agent/crawl/__init__.py b/agent/crawl/__init__.py deleted file mode 100644 index 4bc90fb..0000000 --- a/agent/crawl/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -$Id$ -""" - diff --git a/agent/crawl/base.py b/agent/crawl/base.py deleted file mode 100644 index 04575fe..0000000 --- a/agent/crawl/base.py +++ /dev/null @@ -1,59 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Filesystem crawler. - -$Id$ -""" - -from zope.interface import implements - -from loops.agent.interfaces import ICrawlingJob, IMetadataSet -from loops.agent.schedule import Job - - -class CrawlingJob(Job): - - implements(ICrawlingJob) - - baseProperties = ('starttime', 'type', 'repeat', 'transportType',) - - def __init__(self, **params): - self.predefinedMetadata = {} - super(CrawlingJob, self).__init__(**params) - - def execute(self): - return self.collect() - - -class Metadata(dict): - - implements(IMetadataSet) - - def __init__(self, data=dict()): - for k in data: - self[k] = data[k] - - def asXML(self): - # TODO... - return '' - - def set(self, key, value): - self['key'] = value - diff --git a/agent/crawl/filesystem.py b/agent/crawl/filesystem.py deleted file mode 100644 index 45ac986..0000000 --- a/agent/crawl/filesystem.py +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Filesystem crawler. - -$Id$ -""" - -import os -from fnmatch import filter -from datetime import datetime -from twisted.internet.defer import Deferred -from twisted.internet.task import coiterate -from zope.interface import implements - -from loops.agent.interfaces import IResource -from loops.agent.crawl.base import CrawlingJob as BaseCrawlingJob -from loops.agent.crawl.base import Metadata - - -class CrawlingJob(BaseCrawlingJob): - - def collect(self): - self.collected = [] - coiterate(self.crawlFilesystem()).addCallback(self.finished) - # TODO: addErrback() - self.deferred = Deferred() - return self.deferred - - def finished(self, result): - self.deferred.callback(self.collected) - - def crawlFilesystem(self): - directory = self.params.get('directory') - pattern = self.params.get('pattern') or '*' - lastRun = self.params.get('lastrun') or datetime(1980, 1, 1) - for path, dirs, files in os.walk(directory): - if '.svn' in dirs: - del dirs[dirs.index('.svn')] - self.loadFiles(files, pattern) - - def loadFiles(self, files, pattern): - for f in filter(files, pattern): - filename = os.path.join(path, f) - mtime = datetime.fromtimestamp(os.path.getmtime(filename)) - if mtime <= lastRun: # file not changed - continue - meta = dict( - path=filename, - ) - self.collected.append(FileResource(filename, Metadata(meta))) - yield None - - -class FileResource(object): - - implements(IResource) - - def __init__(self, path, metadata=None): - self.path = path - self.metadata = metadata - - application = 'filesystem' - - @property - def data(self): - return open(self.path, 'r') - diff --git a/agent/crawl/filesystem.txt b/agent/crawl/filesystem.txt deleted file mode 100644 index 5bb1302..0000000 --- a/agent/crawl/filesystem.txt +++ /dev/null @@ -1,42 +0,0 @@ -===================================================== -loops.agent.crawl.filesystem - The Filesystem Crawler -===================================================== - - ($Id$) - - >>> import os - >>> from time import time - - >>> from loops.agent.tests import tester, baseDir - >>> from loops.agent.core import Agent - >>> from loops.agent.crawl.filesystem import CrawlingJob - - >>> agent = Agent() - >>> startTime = scheduler = agent.scheduler - -We create a crawling job that should scan the data subdirectory -of the testing directory in the loops.agent package. - - >>> dirname = os.path.join(baseDir, 'testing', 'data') - >>> crawlJob = CrawlingJob(directory=dirname) - -The result of the crawling process should be transferred using -the dummy transporter from the testing package; this just prints -an informative message with the contents of the files to be -transferred. - - >>> from loops.agent.testing import transport - >>> transporter = transport.Transporter(agent) - >>> transportJob = transporter.createJob() - >>> crawlJob.successors.append(transportJob) - -We are now ready to schedule the job and let the reactor execute it. - - >>> startTime = scheduler.schedule(crawlJob) - - >>> tester.iterate() - Metadata: {'path': '...data...subdir...file2.txt'} - Transferring: Data from file2.txt - Metadata: {'path': '...data...file1.txt'} - Transferring: Data from file1.txt - diff --git a/agent/crawl/outlook.py b/agent/crawl/outlook.py deleted file mode 100644 index 91d6c75..0000000 --- a/agent/crawl/outlook.py +++ /dev/null @@ -1,277 +0,0 @@ -""" -This module reads out information from Microsoft Outlook. - -The function loadInbox() reads all Emails of MsOutlook-inbox folder, -optionally it is possible to read the subfolder of the inbox too. -The emails will be returnes as Python MIME objects in a list. - -Tobias Schmid 26.07.2007 -""" - -import win32com.client -import ctypes -import win32api, win32process, win32con -import re -from email.mime.multipart import MIMEMultipart - -from watsup.winGuiAuto import findTopWindow, findControl, findControls, clickButton, \ - getComboboxItems, selectComboboxItem, setCheckBox - -from twisted.internet.defer import Deferred -from twisted.internet.task import coiterate -from twisted.internet import threads -from zope.interface import implements - -from loops.agent.interfaces import IResource -from loops.agent.crawl.base import CrawlingJob as BaseCrawlingJob -from loops.agent.crawl.base import Metadata - - -# DEBUG FLAGS -DEBUG = 1 -DEBUG_WRITELINE = 1 - -# some constants -COMMASPACE = ', ' - - -class CrawlingJob(BaseCrawlingJob): - - keys = "" - inbox = "" - subfolders = "" - pattern = "" - - def collect(self): - self.collected = [] - coiterate(self.crawlOutlook()).addCallback(self.finished) - # TODO: addErrback() - self.deferred = Deferred() - return self.deferred - - - def finished(self, result): - self.deferred.callback(self.collected) - - - def crawlOutlook(self): - outlookFound = 0 - try: - oOutlookApp = \ - win32com.client.gencache.EnsureDispatch("Outlook.Application") - outlookFound = 1 - except: - print "MSOutlook: unable to load Outlook" - - records = [] - - if not outlookFound: - return - - # fetch the params - 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 '.*') - else: - self.pattern = None - - - if DEBUG_WRITELINE: - print 'MSOutlook.loadInbox() ===> starting' - - # try to handle the Outlook dialog - #d = threads.deferToThread(self.handleOutlookDialog) - #d.addCallback(self.printResult) - - # catch Inbox folder - onMAPI = oOutlookApp.GetNamespace("MAPI") - ofInbox = \ - onMAPI.GetDefaultFolder(win32com.client.constants.olFolderInbox) - - # fetch the mails of the inbox folder - if DEBUG_WRITELINE: - print 'MSOutlook.loadInbox() ===> fetch mails of inbox folder' - - # fetch mails from inbox - if self.inbox: - self.loadEmail(ofInbox) - - # fetch mails of inbox subfolders - if self.subfolders and self.pattern is None: - - if DEBUG_WRITELINE: - print 'MSOutlook.loadInbox() ===> fetch emails of subfolders' - - lInboxSubfolders = getattr(ofInbox, 'Folders') - for of in range(lInboxSubfolders.__len__()): - # get a MAPI-folder object and load its emails - self.loadEmail(lInboxSubfolders.Item(of + 1)) - - # pattern, just read the specified subfolder - elif self.subfolders and self.pattern: - - if DEBUG_WRITELINE: - print 'MSOutlook.loadInbox() ===> fetch emails of specified subfolder' - lInboxSubfolders = getattr(ofInbox, 'Folders') - for of in range(lInboxSubfolders.__len__()): - # get a MAPI-folder object and load its emails - if self.pattern.match(getattr(lInboxSubfolders.Item(of + 1), 'Name')): - self.loadEmail(lInboxSubfolders.Item(of + 1)) #oFolder - - - if DEBUG: - print 'number of mails in Inbox:', len(ofInbox.Items) - # list of _Folder (subfolder of inbox) - lInboxSubfolders = getattr(ofInbox, 'Folders') - # get Count-Attribute of _Folders class - iInboxSubfoldersCount = getattr(lInboxSubfolders, 'Count') - # the Item-Method of the _Folders class returns a MAPIFolder object - oFolder = lInboxSubfolders.Item(1) - - print 'Count of Inbox-SubFolders:', iInboxSubfoldersCount - print 'Inbox sub folders (Folder/Mails):' - for folder in range(iInboxSubfoldersCount): - oFolder = lInboxSubfolders.Item(folder+1) - print getattr(oFolder, 'Name'), '/' , len(getattr(oFolder, 'Items')) - - - if DEBUG_WRITELINE: - print 'MSOutlook.loadInbox() ===> ending' - yield '1' - - - def loadEmail(self, oFolder): - # get items of the folder - folderItems = getattr(oFolder, '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) - - if DEBUG: - self.keys.sort() - print 'Fiels\n=======================================' - for key in self.keys: - print key - - record = {} - for key in self.keys: - record[key] = getattr(mail, key) - if DEBUG: - print str(item) - - # Create the mime email object - msg = self.createEmailMime(record) - - # list with mime objects - self.collected.append((OutlookResource(msg))) - - - def createEmailMime(self, emails): - # Create the container (outer) email message. - msg = MIMEMultipart() - # subject - msg['Subject'] = emails['Subject'].encode('utf-8') - - # sender - if emails.has_key('SenderEmailAddress'): - sender = str(emails['SenderEmailAddress'].encode('utf-8')) - else: - sender = str(emails['SenderName'].encode('utf-8')) - msg['From'] = sender - - # CC - #msg['CC'] = str(emails['CC'].encode('utf-8')) - - # ReceivedTime - #msg['Date'] = str(emails['ReceivedTime']) - - #recipients - 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) - - # message - msg.preamble = emails['Body'].encode('utf-8') - - return msg - - - 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) - print 'searching....' - 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) - print '===> MSOutlook dialog box found' - #tid, pid = win32process.GetWindowThreadProcessId(hwnd) - - # get a handle - #handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid) - # terminate the process by the handle - #win32api.TerminateProcess(handle, 0) - # close the handle - thankyou - #win32api.CloseHandle(handle) - - # 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) - print '-> dialog found and handled' - break - - def printResult(self): - print '--> Outlook dialog handled' - - -class OutlookResource(object): - - implements(IResource) - - def __init__(self, oEmail): - self.oEmail = oEmail - - @property - def data(self): - return self.oEmail - diff --git a/agent/crawl/watsup/AppControls.py b/agent/crawl/watsup/AppControls.py deleted file mode 100644 index dee6f64..0000000 --- a/agent/crawl/watsup/AppControls.py +++ /dev/null @@ -1,152 +0,0 @@ -""" Class and functions to find the windowText and className for a given executable -""" - -# Author : Tim Couper - tim@2wave.net -# Date : 22 July 2004 -# Version : 1.1 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. -# Notes : Requires Python 2.3, win32all, ctypes & watsup package - -import os.path - -from watsup.winGuiAuto import findTopWindows,findControls,dumpWindow,\ - dumpTopWindows -from watsup.utils import dumpHwnd,tupleHwnd -from watsup.launcher import AppThread,terminateApp -from watsup.OrderedDict import OrderedDict -from time import time,sleep -from types import ListType - -THREAD_TIMEOUT_SECS=2.0 -INDENT=2 - -#wstruct is of the form [hwnd,wclass,wtext] or -# [hwnd,wclass,wtext,[winstruct] - -##def printwstruct(wstruct): -## print 'printFormat had trouble with:\n%s' % str(wstruct[3]) -## import pprint -## print '------' -## pprint.pprint(wstruct) -## print '------' - -def printFormat(wstruct,indent=0): - def printwstruct(wstruct): - print 'printFormat had trouble with:\n%s' % str(wstruct[3]) - import pprint - print '------' - pprint.pprint(wstruct) - print '------' - """wstruct is either a wintuple, or a recursive list of wintuples - """ - - # type 1 - if type(wstruct[1]) is not ListType: - print '%s%s: %s' % (' '*indent,wstruct[2],wstruct[1]) - if len(wstruct)>3 and wstruct[3]<>None: - try: - for item in wstruct[3]: - printFormat(item,indent+INDENT) - except: - print_wstruct(wstruct) - # type 2: - else: - for item in wstruct[1]: - printFormat(item,indent+INDENT) - - -class AppControls(object): - def __init__(self,program,wantedText=None,wantedClass=None,selectionFunction=None,verbose=False): - - self.wantedText=wantedText - self.wantedClass=wantedClass - self.selectionFunction=selectionFunction - - self.verbose=verbose - - topHwnds,unwantedHwnds,self.appThread=findAppTopWindows(program,verbose=verbose) #should only be one, but you never know - - self.topHwnds=[] - self.unwantedHwnds=unwantedHwnds - - def run(self): - while self.appThread.isAlive(): - results=findNewTopWindows(self.unwantedHwnds,self.verbose) - if results: - self.process(results) # update the list of topWindows and unwanted TopWindows - - def process(self,results): - for hwnd in results: - - ctHwnds=findControls(hwnd) - if ctHwnds: - # we only add hwnd if there are controlHwnds - # as there may be a form which exists - # as an hwnd, but has not controls materialised yet - self.unwantedHwnds.append(hwnd) - self.write(hwnd) - for ctHwnd in ctHwnds: - self.unwantedHwnds.append(ctHwnd) - - def write(self,hwnd): - h=tupleHwnd(hwnd) - t=[h[0],h[1],h[2],dumpWindow(hwnd)] - printFormat(t) - -def findNewTopWindows(unwantedHwnds=[],verbose=False): - # returns a list of all top windows' hwnds we haven't - # found yet - htuples=dumpTopWindows() - - if verbose: - print '..%d windows found (%d windows in unwanted)' % (len(htuples),len(unwantedHwnds)) - results=[] - for htuple in htuples: - hwnd=htuple[0] - if hwnd not in unwantedHwnds: - if verbose: - print '..adding %s' % dumpHwnd(hwnd) - results.append(hwnd) - - return results - -def findAppTopWindows(program,verbose=False): - """returns the hwnds for the program, along with the hwnds for the - stuff that are to be ignored - """ - - # first we run findTopWindows before launching the program; store the hwnds found - # (note that it doesn't matter if an instance of the program IS running - # as we'll start another one whose hwnds will be new) - - # run findTopWindows, and remove from the list any which are stored - unwantedHwnds=findNewTopWindows() # list of topWindow hwnds that exist - if verbose: - print '..%d original window(s)' % len(unwantedHwnds) - # run the program - appThread=AppThread(program,verbose) - appThread.start() - # give the thread a chance to get going: - results=[] - t=time() - while not results and ((time()-t)None: - for cp in controlParameters: - hwnd=cp.get('hwnd',None) - wantedClass=cp.get('wantedClass',None) - wantedText=cp.get('wantedTest',None) - selectionFunction=cp.get('selectionFunction',None) - clist=self.findControls(hwnd=hwnd, - wantedText=wantedText, - wantedClass=wantedClass, - selectionFunction=selectionFunction) - - self.controls.extend(clist) - - self._mainMenuHandle=None - - def activateMenuItem(self,menuItem): - menuItem.activateMenuItem() - - def terminate(self): - terminateApp(self.hwnd) - -#------------------------------------------------------------------------------- -#Accessors & properties - -### top menu item -## def getMainMenu(self): -## if self._mainMenuHandle: -## return self._mainMenuHandle -## else: -## return getTopMenu(self.hwnd) -## -## mainMenu=property(getMainMenu) -#------------------------------------------------------------------------------- - -class PControlError(Exception): pass -class PControl(PWinControl): - def __init__(self,parent,hwnd=None,wantedText=None,wantedClass=None,selectionFunction=None): - "Constructor takes either hwnd directly, or others in a controlParameter set" - PWinControl.__init__(self,parent) - if hwnd: - self.hwnd=hwnd - else: - try: - self.hwnd=findControl(parent.hwnd, - wantedText=wantedText, - wantedClass=wantedClass, - selectionFunction=selectionFunction, - maxWait=CONTROL_MAX_WAIT_SECS) - except WinGuiAutoError,e: - raise PControlError,e - - -## def addKnowledge(self,attrName): -## knowledge=getKnowledge() -## knowledge.add(self.className,attrName) - - #------------------------------------------------------------------------------- - # general winctrl actions which, if acted upon by a user, might tell us sth about - # this unknown control - - def getItems(self): - # This PControl of unknown class can get items - # at least the user thinks so - #self.addKnowledge('getItems') - res=getComboboxItems(self.hwnd) - if not res: - res=getListboxItems(self.hwnd) - - return res - - def selectItem(self,item): - # This PControl of unknown class can select items - # at least the user thinks so - #self.addKnowledge('selectItem') - res= selectListboxItem(self.hwnd, item) - if not res: - res=selectComboBoxItem(self.hwnd,item) - return res - - def click(self): - # This PControl of unknown class is clickable - # at least the user thinks so - #self.addKnowledge('click') - clickButton(self.hwnd) - - def getCaption(self): - # This PControl of unknown class has a caption, - # at least the user thinks so - #self.addKnowledge('caption') - return self.getEditText() - - def setCheckBox(self, state=3DTrue): - setCheckBox(self.hwnd, state) - - def getCheckBox(self): - return getCheckBox(self.hwnd) - - #------------------------------------------------------------------------------- - #Accessors and properties - - def getText(self): - # returns a list of strings which make up the text - return getEditText(self.hwnd) - - def setText(self,text,append=False): - setEditText(text,append) - - def getClassName(self): - return win32gui.GetClassName(self.hwnd) - - text=property(getText) - className=property(getClassName) - caption=property(getCaption) - items=property(getItems) - - #------------------------------------------------------------------------------- - -class PEdit(PControl): - def __init__(self,parent,hwnd=None,wantedText=None,wantedClass=None,selectionFunction=None): - PControl.__init__(self,parent,hwnd,wantedText,wantedClass,selectionFunction) - - #------------------------------------------------------------------------------- - #Accessors and properties - - def getText(self): - # returns a simple string - PEdit controls only have one value - p=PControl.getText(self) - if p: - return p[0] - else: - return '' - - text=property(getText) - caption=None #undefine the caption property - - #------------------------------------------------------------------------------- - -class PText(PControl): - # multi-line text control - caption=None - -class PComboBox(PControl): - - def selectItem(self,item): - selectComboboxItem(self.hwnd,item) - - #------------------------------------------------------------------------------- - #Accessors and properties - - def getItems(self): - return getComboboxItems(self.hwnd) - - items=property(getItems) - - - #------------------------------------------------------------------------------- - - -class PDelphiComboBox(PComboBox): - # The Delphi Combo box has a control of class Edit within - # it, which contains the text - def __init__(self,parent,hwnd=None,wantedText=None,wantedClass=None,selectionFunction=None): - PControl.__init__(self,parent,hwnd,wantedText,wantedClass,selectionFunction) - self.editCtrl=self.findPControl(wantedClass='Edit') - #------------------------------------------------------------------------------- - #Accessors and properties - - def getText(self): - # get the content from the control Edit: - - return self.editCtrl.getText() - - text=property(getText) - - - #------------------------------------------------------------------------------- - -class PButton(PControl): - - def click(self): - clickButton(self.hwnd) - - #------------------------------------------------------------------------------- - #Accessors and properties - - caption=property(PControl.getText) - - #------------------------------------------------------------------------------- - -class PListBox(PControl): - - def selectItem(self,item): - return selectListboxItem(self.hwnd, item) - - #------------------------------------------------------------------------------- - #Accessors and properties - - def getItems(self): - return getListboxItems(self.hwnd) - - items=property(getItems) - - - #------------------------------------------------------------------------------- - -class PCheckBox(PControl): - - - #------------------------------------------------------------------------------- - #Accessors and properties - - - caption=property(PControl.getText) - - def getCheckStatus(self): - return self.getCheckBox() - - def isChecked(self): - return self.getCheckStatus()#=3D=3D win32con.BST_INDETERMINATE - - def isIndeterminate(self): - return self.getCheckStatus() #=3D=3D win32con.BST_INDETERMINATE - =20 - - def isNotChecked(self): - return self.getCheckStatus() #=3D=3D win32con.BST_UNCHECKED - =20 - - def setChecked(self): - setCheckBox(hwnd, state = True) - - def setUnChecked(self): - setCheckBox(hwnd, state = False) - - - #------------------------------------------------------------------------------- -KNOWN_CLASSES={'TEdit': PEdit, - 'TComboBox': PDelphiComboBox, - 'ComboBox': PComboBox, - 'TButton': PButton, - 'TListBox': PListBox, - 'ListBox': PListBox, - 'CheckBox': PCheckBox, - 'TCheckBox': PCheckBox, - - } - \ No newline at end of file diff --git a/agent/crawl/watsup/__init__.py b/agent/crawl/watsup/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/agent/crawl/watsup/docs/WatsupIE methods.doc b/agent/crawl/watsup/docs/WatsupIE methods.doc deleted file mode 100644 index ae7b75cd8d7de5ab4c4bc8d0f98893b25b8e2600..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42496 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 av+hg1=cT8Z_fGxyi`r{>{<8x?0{;&(mNTOO diff --git a/agent/crawl/watsup/docs/html/code/example1.py b/agent/crawl/watsup/docs/html/code/example1.py deleted file mode 100644 index a261280..0000000 --- a/agent/crawl/watsup/docs/html/code/example1.py +++ /dev/null @@ -1,10 +0,0 @@ -from watsup.winGuiAuto import findTopWindow,findControl,setEditText -from time import sleep -# Locate notepad's edit area, and enter various bits of text. - -notepadWindow = findTopWindow(wantedClass='Notepad') -editArea = findControl(notepadWindow,wantedClass="Edit") -setEditText(editArea, "Hello, again!") -sleep(0.8) -setEditText(editArea, " You still there?",True) - diff --git a/agent/crawl/watsup/docs/html/code/example2.py b/agent/crawl/watsup/docs/html/code/example2.py deleted file mode 100644 index a6bf70c..0000000 --- a/agent/crawl/watsup/docs/html/code/example2.py +++ /dev/null @@ -1,27 +0,0 @@ -from watsup.winGuiAuto import findControl,setEditText, findTopWindow,clickButton -import os -import os.path - -FILENAME='atestfile.txt' - -def main(): - # delete any occurrence of this file from the disk - if os.path.exists(FILENAME): - os.remove(FILENAME) - - form=findTopWindow(wantedText='Simple Form') - button=findControl(form,wantedText='Create file') - editbox=findControl(form,wantedClass='TEdit') - - # enter a filename: - setEditText(editbox,[FILENAME]) - clickButton(button) - - # now check that the file is there - if os.path.exists(FILENAME): - print 'file %s is present' %FILENAME - else: - print "file %s isn't there" % FILENAME - -if __name__=='__main__': - main() \ No newline at end of file diff --git a/agent/crawl/watsup/docs/html/code/example3.py b/agent/crawl/watsup/docs/html/code/example3.py deleted file mode 100644 index 6f8cb1c..0000000 --- a/agent/crawl/watsup/docs/html/code/example3.py +++ /dev/null @@ -1,17 +0,0 @@ -from watsup.launcher import launchApp,terminateApp -from watsup.winGuiAuto import findTopWindows -import example2 - -# find an instance of SimpleForm. If one isn't there, launch it -forms=findTopWindows(wantedText='Simple Form') -if forms: - form=forms[0] -else: - form=launchApp('simple.exe',wantedText='Simple Form') - -example2.main() - -# and terminate the form -terminateApp(form) - - diff --git a/agent/crawl/watsup/docs/html/code/example4.py b/agent/crawl/watsup/docs/html/code/example4.py deleted file mode 100644 index 8e82bd8..0000000 --- a/agent/crawl/watsup/docs/html/code/example4.py +++ /dev/null @@ -1,51 +0,0 @@ -from watsup.launcher import launchApp,terminateApp -from watsup.winGuiAuto import findTopWindows, findControl,getEditText,clickButton -from watsup.performance import PerformanceCheck,PerformanceCheckError -from time import sleep,time - -def main(myExecutable,myWantedText): - # find an instance of SimpleForm. If one isn't there, launch it - forms=findTopWindows(wantedText=myWantedText) - if forms: - form=forms[0] - else: - form=launchApp(myExecutable,wantedText=myWantedText) - - button=findControl(form,wantedText='Click me') - editbox=findControl(form,wantedClass='TEdit') - - #start a performance check instance - p=PerformanceCheck() - - clickButton(button) - - # belts and braces to avoid infinite waiting! - maxWaitTime=2.0 - startTime=time() - - while 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ⅇGQC>g9=_ z{seH(3MsLb4H**vRm`w?;H}}VCjr|TyV#(-bo@<8Z^Unr1cE5*iR7;m#2Xi1e4o#Q z?7+okU*^!=`y=H|A{8VG_Mh)vtnV1eMU^nmH`l^%jhuvF*5^vnQzpZPo|&C9CcXJu zW1GX!7SD1x;bXHBA@)XN<0KH7fHWckc4SJC-H(n)SVpJ zORe|ulttD^`R2PIR}6wwLHg7X^G#TSL|mJtd1}^Jhl^!Ve;;f(k0( zWixh-LB@Js-%@CN@luH?gE?Dc1e>zj5?{UJ>y!YGCaF>U{DN&8p(ym0cZaa)^@=1< z9@ZKRc|N!ji649eJoUpVAd>ooYag)OC-eNCNc-7mILkOt#O}*{xglYwa+<1(_gUd` zV^emd1aOKZPNsYv-L+5rijd5w?4B)P@zQ)1yIX;7BC#FYJkpqv_WN9CWHQ57`9TWW zCymJ&E0x^<60S91nMq}Z4(Q`K!OkVs>EO8Ssep4#wQfeGaPo}l4c^_CxuO;cLU&BJ zl<_%B6+hW%Fl~2+R`!VbOnV~l*6SbDk4Q)-Xf`SucP%E|TI8i{v)17Y5PxP_HKNn| z9yWpquJGfsx#5*BE>Tx_bu4#*(oUP)u&LQ2uZqfU;gqc)o7h;3gL-J=`-hMU;H=sq z-kDpo6W7zV;L_vXTp3elyCbecF@dCa+7@T*PpovSHH7XnlUWxi^)J0&OXP{=>z^UO2^`z{J=JCwSiU!GxIzA5Y43V&%;{Wm!fSjwlVN8(Qd0Eg tHZSzS4*zs#*vd+$Z=ZDs diff --git a/agent/crawl/watsup/docs/html/images/framework1.jpg b/agent/crawl/watsup/docs/html/images/framework1.jpg deleted file mode 100644 index 6abc351d2eeb198cc229051f16de53ae2f97ea54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46739 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 diff --git a/agent/crawl/watsup/docs/html/images/framework2.jpg b/agent/crawl/watsup/docs/html/images/framework2.jpg deleted file mode 100644 index b50497e755da19b0135c6a47deeb426f1fd9f41f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57321 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

DOV4+=DOO59+q^b? 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

5_ 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

FbJfhC_F9*oR%QXG!fuzUm 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 diff --git a/agent/crawl/watsup/docs/html/intro.html b/agent/crawl/watsup/docs/html/intro.html deleted file mode 100644 index be9809e..0000000 --- a/agent/crawl/watsup/docs/html/intro.html +++ /dev/null @@ -1,343 +0,0 @@ - - - -Windows Application Test System Using Python - - - - - - -

WATSUP - Windows Application Test System Using Python

- -The WATSUP toolkit is designed to allow the automated test of Windows applications. -The system uses the "object-based" mechanism for identifying and invoking actions -on controls and menu items. -

So much has been written about the scope, robustness, scalability and outstanding - usability of Python that I'll go no further with it here, only to say that if - you haven't yet had a chance to use this comprehensive, open source language, - don't miss out on the opportunity to take a serious look at it!

- -

The examples in this document assume a basic familiarity with Python.

- - - -

Functional Tests

- -Testers/developers write automated functional tests which follow a prescriptive, -possibly branching, possibly dynamic "user workflow". The script can check for -changes in the gui itself, operating system environment, file system, database -table and records, network, internet or extranet urls/pages/web services ... - in -fact anywhere that there could be changes. -

-Examination of the functions in module autoWinGui.py within the watsup package shows the variety of windows control items -that can be checked/modified. These include: -

    -
  • Get and set text in editable controls
  • -
  • Edit and select items from controls supporting lists
  • -
  • Click and double-click controls to invoke their actions
  • -
  • Determine the state of menu items and invoke them
  • -
-

The system also provides tools for finding windows by caption and/or class, - controls by text/caption and/or class, and menu items by text or position. (One - of the aspirations of this project is to continue to extend the list to include - as many controls as possible) .

- -

Example 1 - automated writing on Notepad

-

Here's a simple example of the control over applications that you can have with watsup. -First, launch notepad from:

Windows Start Menu - All Programs - Accessories - Notepad

-Then run the following script (Example 1) - -
-from watsup.winGuiAuto import findTopWindow,findControl,setEditText
-from time import sleep  
-# Locate notepad's edit area, and enter various bits of text.
-
-notepadWindow = findTopWindow(wantedClass='Notepad')
-editArea = findControl(notepadWindow,wantedClass="Edit") 
-setEditText(editArea, "Hello, again!")  
-sleep(0.8) 
-setEditText(editArea, " You still there?",True) 
- 
- Finally, close notepad.

- -

Example 2 - testing a simple example

- -In functional tests, the tester wants to ensure that -the cause - invoking a sequence of windows events (button clicks, menu item activation) -has the predicted effect of, for example, a change in a value of a sindows control, -the creation of a file, or the entry of a new database record. -See the directory watsup/examples/simple directory for the executable simple.exe. - -If you run the application, you see a button and a text box. -Enter a valid filename into the box, say xyz, and -click the button; -after the file is created, a message box appears containing a success message, -and investigation of the directory watsup/examples/simple will show a file -called 'xyz.txt' has been created (or overwritten). - -

Now let's script a test to automate this functionality.

- -First find and launch the application. -Then run the following script (Example 2) -
-from watsup.winGuiAuto import findControl,setEditText, findTopWindow,clickButton
-import os
-import os.path
-
-FILENAME='atestfile.txt'
-
-def main():
-    # delete any occurrence of this file from the disk
-    if os.path.exists(FILENAME):
-        os.remove(FILENAME)
-        
-    form=findTopWindow(wantedText='Simple Form')
-    button=findControl(form,wantedText='Create file')
-    editbox=findControl(form,wantedClass='TEdit')
-    
-    # enter a filename:
-    setEditText(editbox,[FILENAME])
-    print 'clicking button to create file'
-		clickButton(button)
-    
-    # now check that the file is there
-    if os.path.exists(FILENAME):
-        print 'file %s is present' %FILENAME
-    else:
-        print "file %s isn't there" % FILENAME     
-
-if __name__=='__main__':
-    main()    
-
-
-
-
- - -

Example 3 - automating program launch and termination

- -

It's a bit tedious having to start and close the application each time. -Example 3 launches the application, -if it isn't already running, and terminates it on -completion of the test

- -
-from watsup.launcher import launchApp,terminateApp
-from watsup.winGuiAuto import findTopWindows                            
-import example2
-
-# find an instance of SimpleForm. If one isn't there, launch it
-
-forms=findTopWindows(wantedText='Simple Form')
-if forms:
-    form=forms[0]
-else:
-    form=launchApp('simple.exe',wantedText='Simple Form')    
-
-example2.main()
-
-# and terminate the form
-terminateApp(form)    
-
-
- -launchApp starts the application in a separate thread, -and looks for a window with caption containing "Simple Form", -returning the window handle of the recovered form. - -terminateApp attempts to close the form, by trying to activate menu item File-Exit, or, failing that, -sending Alt + F4. - -

Example 4 - finding windows and controls

- -

In building scripts, we need to be able to find the class and/or text of the many windows and controls -to be investigated or invoked.

- -

In the tools directory, there's a tool - ShowWindows.bat - to assist us with this.

-Show Windows 1 - - -

Clicking the "Register" button persists information about -the existing windows running on the system (and it tells you how many, FYI). -Clicking the "Find new" button will report all non-trivial windows and all their -constituent controls which have appeared in the windows environment -since the last "Register" click. - -So to test our program simple.exe, launch ShowWindows, click Register. -Then launch simple.exe and -and click the Find New button. -The associated text box shows n/m, where m is the total number of new windows found, -and n is the number of those which are significant (ie have any controls) and are reported.

- -Show Windows 2 - - -

Performance Tests

- -

Performance tests, in this definition, are single-client scripts, -which are similar in operation to the functional tests above, -but for which certain steps of the tests -must either be done within an acceptable timeframe ("CHECKING") -and/or the time taken for those steps -should be recorded for subsequent analysis ("RECORDING").

- -

WATSUP provides a simple mechanism to add such tests to existing functional tests. -In examples 4a & 4b, we launch almost identical applications, -perform.exe and perform2.exe respectively. - When the button is clicked, the text "Finished" is written -to the edit box. The difference between the programs is that the former is - coded to wait for 1/2 second before "Finished" -appears; in the latter case, the delay is 1.5 seconds.

- -

In both cases, we are setting a performance test that the process -take no more than 1 second. -Clearly, we should expect example 4a to be ok and example 4b to fail. -

- - -

So we have Example 4a

-
-from example4 import main
-
-main('perform.exe','Performance Form 1')
-
-
-

and Example 4b

-
-from example4 import main
-
-main('perform2.exe','Performance Form 2')
-
-
- -

which reference Example 4:

-
-
-from watsup.launcher import launchApp,terminateApp 
-from watsup.winGuiAuto import findTopWindows, findControl,getEditText,clickButton  
-from watsup.performance import PerformanceCheck,PerformanceCheckError  
-from time import sleep,time                      
-
-def main(myExecutable,myWantedText):
-    # find an instance of SimpleForm. If one isn't there, launch it
-    forms=findTopWindows(wantedText=myWantedText)
-    if forms:
-        form=forms[0]
-    else:
-        form=launchApp(myExecutable,wantedText=myWantedText)       
-    
-    button=findControl(form,wantedText='Click me')
-    editbox=findControl(form,wantedClass='TEdit')
-
-    #start a performance check instance
-    p=PerformanceCheck()
-    
-    clickButton(button)    
-    
-    # belts and braces to avoid infinite waiting!
-    maxWaitTime=2.0
-    startTime=time()
-    
-    while time()-startTime<maxWaitTime:
-        t=getEditText(editbox)
-        if t:
-            break
-        else:
-            sleep(0.1)
-    else:
-        raise Exception,'Failed to get value after maxWaitTime of %s secs' % maxWaitTime        
-    
-    try:
-        try:
-           #do the check/recording step, identifying this step with the wantedtext
-            p.check(myWantedText,1.0)    
-        except PerformanceCheckError,e:
-            print '** Failed: %s' % e
-            
-    # and terminate the form
-    finally:
-        terminateApp(form)      
-
-if __name__=='__main__':
-    print ' please run example4a or 4b'
-
-
- -

Key points in example4.py

- -

Immediately prior to clicking the button, we establish a PerformanceCheck instance, -which, among other things, establises a timer. -Every time the check() method is called on a PerformanceCheck instance, the following occurs:

- -

Also if we are CHECKING (the default condition), -that time is checked against the number of seconds added as the -second parameter. -If the elapsed time exceeds the value of the 2nd parameter, an exception is raised.

- -

So in example4.py, we see that the check method requires a 1 second acceptance. -Hence example4a succeeds and example4b fails. -

- -

Regression Testing

- -

Functional and performance scripts such as those above are -immediately useable within a test framework - -the excellent python unit test framework is highly recommended -(unittest.py, which comes along with your python installation). -This then enables the tester to develop complete regression tests -involving any combination of Functional & Performance testing.

-

For an example of the use of functional and performance tests within -the unit test framework, run the program framework.bat in the tools subdirectory. -This will launch a nice user interface from which sets of test cases can be run: -

- - -

Select the File menu option, and you have the option to load files which -contain testcases, or directories/directory trees. If select the "Load files" -option, and navigate up one directory, and then down through examples and the unittests -directories, you should find, and select, exampleTests.py. -The Framework program examines the selected file(s) and extracts the TestCases, -presents in the top frame

- - - -

Selecting Menu option Testing - Run Tests (or the long button labelled Run Tests), - will cause each test to be run; success or failure is shown in the lower frame

- - -

For more information on python's unit test module, refer to the python documentation. -Finally, it is worth noting that this framework will work with any unit tests, not just -those specifically testing windows application, so you can test -elements of the logic that you have written in other applications. - -

Downloads

- -Download watsup here -

(This package should be unzipped in the site-packages directory in your python installation)

- -

Dependencies

- - -

Credits

- -

The framework test tool was built from parts in unittestgui.py by Chris Liechti. -Much of the core functionality in WATSUP derives from the important work by - Simon Brunning, - who, in his module winGuiAuto.py provided concise, accessible mechanisms to - access and "click" windows controls & menus; Simon's work recognises the huge - contribution that Mark Hammond has made to the python community (and wider) - in providing pywin32, comprehensive win32 API modules for python. -

- -Dr Tim Couper
-timc@tizmoi.net - - - diff --git a/agent/crawl/watsup/docs/images/tizmoi.jpg b/agent/crawl/watsup/docs/images/tizmoi.jpg deleted file mode 100644 index eccd841395c5ff450028960a4685b18248b9447c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6827 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 diff --git a/agent/crawl/watsup/docs/tac.css b/agent/crawl/watsup/docs/tac.css deleted file mode 100644 index 3ca6dbd..0000000 --- a/agent/crawl/watsup/docs/tac.css +++ /dev/null @@ -1,7 +0,0 @@ -body {font-family: "Trebuchet MS", Verdana, sans-serif} -code {font: Courier New; size 12} -h1 {color: navy} -h2 {color: navy} -h3 {color: navy} -h4 {color: navy} - diff --git a/agent/crawl/watsup/launcher.py b/agent/crawl/watsup/launcher.py deleted file mode 100644 index e34441e..0000000 --- a/agent/crawl/watsup/launcher.py +++ /dev/null @@ -1,116 +0,0 @@ -""" launch & terminate a windows application -""" - -# Author : Tim Couper - tim@2wave.net -# Date : 22 July 2004 -# Version : 1.0 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. -# Notes : Requires Python 2.3, win32all, ctypes, SendKeys & winGuiAuto - - -import os.path -import threading -import win32gui -#import win32api -#import win32con -import SendKeys -from watsup.utils import WatsupError -from watsup.winGuiAuto import findTopWindow,findTopWindows, \ - WinGuiAutoError,activateMenuItem - -MENU_TERMINATIONS=[('file', 'exit'),('file', 'quit')] - -def launchApp(program,wantedText=None,wantedClass=None,verbose=False): - global p - p=AppThread(program,verbose) - p.start() - try: - return findTopWindow(wantedText=wantedText,wantedClass=wantedClass) - except WinGuiAutoError,e: - pass - - # find all the Windows that are lurking about, and terminate them - # this I can't do until terminateApp can find a window and bring it to focus - # for now, find one top window and return that; if there are none, - # it should raise an exception - try: - return findTopWindows(wantedText=wantedText,wantedClass=wantedClass)[0] - except IndexError: - raise WatsupError,'Failed to find any windows' - -class AppThread(threading.Thread): - def __init__(self,program,verbose=False): - threading.Thread.__init__(self,name=program) - self.program=program - self.verbose=verbose - - def run(self): - """main control loop""" - #check the given program exists, and is a file: - # if the program has no type, make it .exe: - if os.path.splitext(self.program)[1]=='': - prog='%s.exe' % self.program - else: - prog=self.program - - prog=os.path.abspath(prog) - - if os.path.isfile(prog): - # launch the new application in a separate thread - if self.verbose: - print '..launching "%s"' % prog - os.system('"%s"' % prog) - - else: - print 'AppThread: program not found: "%s"\n' % prog - - if self.verbose: - print '..terminating "%s"' % prog - - -def terminateApp(hwnd=None): - """Terminate the application by: - If there's a file-exit menu, click that - If there's a file-Quit menu, click that - Otherwise, send Alt-F4 to the current active window -** it's the calling program responsibility to get the right active window - """ - if hwnd: - for fileExit in MENU_TERMINATIONS: - try: - activateMenuItem(hwnd, fileExit) - return - except WinGuiAutoError: - pass - - # blast the current window with ALT F4 .. - SendKeys.SendKeys("%{F4}") - #win32gui.PumpWaitingMessages() -## from watsup.winGuiAuto import findControl,findTopWindows -## topHs=findTopWindows(wantedText='MyTestForm') -## for topH in topHs: -## #hwnd=findControl(topH) -## a=win32gui.findWindow(topH) -## print a, topH -## win32gui.SetFocus(topH) -## win32gui.PumpWaitingMessages() -## SendKeys.SendKeys("%{F4}") -## - -## if not hwnd and (wantedText or wantedClass): -## hwnds=findTopWindows(wantedText=wantedText,wantedClass=wantedClass) -## elif hwnd: -## hwnds=[hwnd] -## -## if hwnds: -## for hwnd in hwnds: -## #win32gui.SetFocus(hwnd) -## #win32gui.SetActiveWindow(hwnd) -## # mm the above don;t work, perhaps because the -## # window is actually to be found in another thread -## -## SendKeys.SendKeys("%{F4}") - -if __name__=='__main__': - pass \ No newline at end of file diff --git a/agent/crawl/watsup/performance.py b/agent/crawl/watsup/performance.py deleted file mode 100644 index 8417f58..0000000 --- a/agent/crawl/watsup/performance.py +++ /dev/null @@ -1,181 +0,0 @@ -# Author : Tim Couper - timc@tizmoi.net -# Date : 22 July 2004 -# Version : 1.0 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. - -from time import time,ctime -import sys -import cPickle - -#------------------------------------------------------------------------------- -# hidden globals - -_PerformanceDataThisRun=None -_PerformanceDataStore=None - -_doPerformanceChecks=True -_recordPerformanceData=False - -#------------------------------------------------------------------------------- -# externals - -def isRecording(): - return _recordPerformanceData - -def isChecking(): - return _doPerformanceChecks - -def doRecording(doIt=True): - global _recordPerformanceData - _recordPerformanceData=doIt - -def doChecking(doIt=True): - global _doPerformanceChecks - _doPerformanceChecks=doIt - -def onOff(bool): - if bool: - return 'On' - else: - return 'Off' - -def getPerformanceDataThisRun(): - "returns the Performance data for this run of the tests" - global _PerformanceDataThisRun - if _PerformanceDataThisRun==None: - # create an instance, and load up what was already there - _PerformanceDataThisRun=PerformanceDataThisRun() - return _PerformanceDataThisRun - -def getPerformanceDataStore(): - "returns the all Performance data for all runs of the tests" - global _PerformanceDataStore - if _PerformanceDataStore==None: - # create an instance, and load up what was already there - _PerformanceDataStore=PerformanceDataStore() - try: - _PerformanceDataStore=cPickle.load(open(_PerformanceDataStore.filename)) - except IOError,EOFError: - pass - return _PerformanceDataStore - -PERFORMANCE_STORE_FILENAME='PerformanceDataStore.dat' - -class PerformanceDataCollectionError(Exception): pass -class PerformanceDataCollection(dict): - def __str__(self): - lines=[] - keys=self.keys() - keys.sort() - for msg in keys: - lines.append(str(msg)) - pdiList=self[msg] - pdiList.sort() - for pdi in pdiList: - lines.append(' %s' % pdi) - return '\n'.join(lines) - -#class PerformanceDataThisRunError(Exception): pass -class PerformanceDataThisRun(PerformanceDataCollection): pass - -class PerformanceDataStoreError(Exception): pass -class PerformanceDataStore(PerformanceDataCollection): - 'performs persistent store of PerformanceDataItems' - def __init__(self, filename=PERFORMANCE_STORE_FILENAME): - self.filename=filename - - def addItem(self,msg,pdi): - 'adds a pdi item to items, in the right place' - # msg must be immutable - if isinstance(pdi,PerformanceDataItem): - if not self.has_key(msg): - self[msg]=[] - self[msg].append(pdi) - else: - e='addItem can only accept PerformanceDataItem instances (%s: %s)' % (msg,str(pdi)) - raise PerformanceDataStoreError,e - -#------------------------------------------------------------------------------- - -class PerformanceCheckError(AssertionError): pass - -class PerformanceCheck(object): - - def __init__(self): - self.reset() - - def reset(self): - self.startTime=time() - - def check(self,msg,maxDelay=None): - res=time()-self.startTime - - # if we're storing the data - if isRecording(): - pdi=PerformanceDataItem(self.startTime,res,maxDelay) - pd=getPerformanceDataThisRun() - if not pd.has_key(msg): - pd[msg]=[] #list of PerformanceData instances, we hope - - # add it to the current data being processed - pd[msg].append(pdi) - - # and add it to the store that we're going to persist - pds=getPerformanceDataStore() - pds.addItem(msg,pdi) - cPickle.dump(pds,open(pds.filename,'w')) - - # if we're acting on performance tests: - if isChecking() and maxDelay<>None and (res>float(maxDelay)): - res=round(res,2) - msg='%s [Delay > %s secs (%s secs)]' % (msg,maxDelay,res) - raise PerformanceCheckError,msg - -class PerformanceDataItem(object): - "holds a result (as elapsed time value in secs) and the time of invocation" - def __init__(self,startTime,elapsedTime,maxDelay=None): - self.startTime=startTime # time of run - self.elapsedTime=round(elapsedTime,2) # elapsed time in secs - self.maxDelay=maxDelay # user defined delay for check - # if None, then there is no upper threshhold - - def __str__(self): - if self.maxDelay==None: - ok='OK' - md='' - else: - if self.elapsedTime<=self.maxDelay: - ok='OK' - else: - ok='Fail' - md= ' (%s)' % self.maxDelay - - return '%s: %s%s %s' % (ctime(self.startTime), self.elapsedTime, md, ok) - - def __cmp__(self,other): - if self.startTimeother.startTime: - return 1 - else: - return 0 - - -def nicePrint(filename=sys.stdout): - def wout(f,text): - f.write('%s\n' % text) - - if filename==sys.stdout: - f=sys.stdout - else: - f=open(filename,'w') - -## from watsup.timedunittest import getPerformanceDataStore,getPerformanceDataThisRun, \ -## isRecording - if isRecording(): - for func in (getPerformanceDataThisRun,getPerformanceDataStore): - fn=func.__name__ - wout(f,'\n%s\n%s\n' % (fn,'-'*len(fn))) - wout(f,str(func())) - diff --git a/agent/crawl/watsup/timedunittest.py b/agent/crawl/watsup/timedunittest.py deleted file mode 100644 index 6413fc6..0000000 --- a/agent/crawl/watsup/timedunittest.py +++ /dev/null @@ -1,279 +0,0 @@ -# Author : Tim Couper - tim@2wave.net -# Date : 22 July 2004 -# Version : 1.0 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. - -from unittest import TestCase -from time import time,sleep,ctime -import sys - -if sys.platform=='win32': - import win32gui - def pump(): - # clear down any waiting messages (it all helps) - win32gui.PumpWaitingMessages() -else: - pump=None - -###------------------------------------------------------------------------------- -### hidden globals -## -##_PerformanceDataThisRun=None -##_PerformanceDataStore=None -##_doPerformanceChecks=True -##_recordPerformanceData=True -###------------------------------------------------------------------------------- -## -###------------------------------------------------------------------------------- -### externals -## -##def isRecording(): -## return _recordPerformanceData -## -##def isChecking(): -## return _doPerformanceChecks -## -##def doRecording(doIt=True): -## global _recordPerformanceData -## _recordPerformanceData=doIt -## -##def doChecking(doIt=True): -## global _doPerformanceChecks -## _doPerformanceChecks=doIt -## -##def getPerformanceDataThisRun(): -## "returns the Performance data for this run of the tests" -## global _PerformanceDataThisRun -## if _PerformanceDataThisRun==None: -## # create an instance, and load up what was already there -## _PerformanceDataThisRun=PerformanceDataThisRun() -## return _PerformanceDataThisRun -## -##def getPerformanceDataStore(): -## "returns the all Performance data for all runs of the tests" -## global _PerformanceDataStore -## if _PerformanceDataStore==None: -## # create an instance, and load up what was already there -## _PerformanceDataStore=PerformanceDataStore() -## try: -## _PerformanceDataStore=cPickle.load(open(_PerformanceDataStore.filename)) -## except IOError,EOFError: -## pass -## return _PerformanceDataStore -## -##PERFORMANCE_STORE_FILENAME='PerformanceDataStore.dat' -## -##class PerformanceDataCollectionError(Exception): pass -##class PerformanceDataCollection(dict): -## def __str__(self): -## lines=[] -## keys=self.keys() -## keys.sort() -## for msg in keys: -## lines.append(str(msg)) -## pdiList=self[msg] -## pdiList.sort() -## for pdi in pdiList: -## lines.append(' %s' % pdi) -## return '\n'.join(lines) -## -###class PerformanceDataThisRunError(Exception): pass -##class PerformanceDataThisRun(PerformanceDataCollection): pass -## -##class PerformanceDataStoreError(Exception): pass -##class PerformanceDataStore(PerformanceDataCollection): -## 'performs persistent store of PerformanceDataItems' -## def __init__(self, filename=PERFORMANCE_STORE_FILENAME): -## self.filename=filename -## -## def addItem(self,msg,pdi): -## 'adds a pdi item to items, in the right place' -## # msg must be immutable -## if isinstance(pdi,PerformanceDataItem): -## if not self.has_key(msg): -## self[msg]=[] -## self[msg].append(pdi) -## else: -## e='addItem can only accept PerformanceDataItem instances (%s: %s)' % (msg,str(pdi)) -## raise PerformanceDataStoreError,e -## -###------------------------------------------------------------------------------- -## -##class PerformanceCheckError(AssertionError): pass -## -##class PerformanceCheck(object): -## -## def __init__(self): -## self.reset() -## -## def reset(self): -## self.startTime=time() -## -## def check(self,msg,maxDelay=None): -## res=time()-self.startTime -## -## # if we're storing the data -## if isRecording(): -## pdi=PerformanceDataItem(self.startTime,res,maxDelay) -## pd=getPerformanceDataThisRun() -## if not pd.has_key(msg): -## pd[msg]=[] #list of PerformanceData instances, we hope -## -## # add it to the current data being processed -## pd[msg].append(pdi) -## -## # and add it to the store that we're going to persist -## pds=getPerformanceDataStore() -## pds.addItem(msg,pdi) -## cPickle.dump(pds,open(pds.filename,'w')) -## -## # if we're acting on performance tests: -## if isChecking() and maxDelay<>None and (res>float(maxDelay)): -## res=round(res,2) -## msg='%s [Delay > %s secs (%s secs)]' % (msg,maxDelay,res) -## raise PerformanceCheckError,msg -## -##class PerformanceDataItem(object): -## "holds a result (as elapsed time value in secs) and the time of invocation" -## def __init__(self,startTime,elapsedTime,maxDelay=None): -## self.startTime=startTime # time of run -## self.elapsedTime=round(elapsedTime,2) # elapsed time in secs -## self.maxDelay=maxDelay # user defined delay for check -## # if None, then there is no upper threshhold -## -## def __str__(self): -## if self.maxDelay==None: -## ok='OK' -## md='' -## else: -## if self.elapsedTime<=self.maxDelay: -## ok='OK' -## else: -## ok='Fail' -## md= ' (%s)' % self.maxDelay -## -## return '%s: %s%s %s' % (ctime(self.startTime), self.elapsedTime, md, ok) -## -## def __cmp__(self,other): -## if self.startTimeother.startTime: -## return 1 -## else: -## return 0 -## - -maxWaitSecsDefault=3.0 -retrySecsDefault=0.2 - -class TimedOutError(AssertionError): pass - -class TimedTestCase(TestCase): - # extends the functionality of unittest.TestCase to - # allow multiple attempts at assertion, failif, for - # a specific length of time. If the test is not successful by the time - # that Then it fails - - def __init__(self, methodName='runTest',maxWaitSecsDefault=1.0,retrySecsDefault=0.2): - TestCase.__init__(self,methodName) - self.maxWaitSecsDefault=float(maxWaitSecsDefault) - self.retrySecsDefault=float(retrySecsDefault) - - def doTimeTest(self,func,*args): - # remove the last 2 args, as they're out timing values: - maxWaitSecs=args[-2] - retrySecs=args[-1] - args=args[:-2] - - if maxWaitSecs==None: - maxWaitSecs=self.maxWaitSecsDefault - else: - try: - maxWaitSecs=float(maxWaitSecs) - except TypeError,e: - e='%s (maxWaitSecs "%s")' % (e,maxWaitSecs) - raise TypeError,e - - if retrySecs==None: - retrySecs=self.retrySecsDefault - else: - try: - retrySecs=float(retrySecs) - except TypeError,e: - e='%s (retrySecs "%s")' % (e,retrySecs) - raise TypeError,e - retrySecs=max(0,retrySecs-.001) # allow for the pump,etc below - - t=time() - while (time()-t)findAppControls.py example/example1 - -c:>findAppControls.py -v example/example1 - -- this does it in verbose mode -""" - -# Author : Tim Couper - tim@tizmoi.net -# Date : 1 August 2004 -# Copyright : Copyright TAC Software Ltd, under Python-like licence. -# Provided as-is, with no warranty. -# Notes : Requires watsup - - -from optparse import OptionParser -parser = OptionParser() -parser.add_option("-v", "--verbose", - - action="store_true", dest="verbose", default=False, - - help="print verbose messages") - -(options, args) = parser.parse_args() -if len(args)<1: - print 'Usage: findAppControls.py ' - -else: - from watsup.AppControls import AppControls - try: - print 'passing',args[0] - a=AppControls(args[0],verbose=options.verbose) - a.run() - - except: - import sys - print '\n** %s **\n' % sys.exc_info()[1] - import traceback - traceback.print_tb(sys.exc_info()[2]) - diff --git a/agent/crawl/watsup/tools/fmShowWindows.py b/agent/crawl/watsup/tools/fmShowWindows.py deleted file mode 100644 index 96b5a8f..0000000 --- a/agent/crawl/watsup/tools/fmShowWindows.py +++ /dev/null @@ -1,127 +0,0 @@ -#Boa:Frame:ShowWindows - -# Author : Tim Couper - tim@tizmoi.net -# Date : 1 August 2004 -# Copyright : Copyright TAC Software Ltd, under Python-like licence. -# Provided as-is, with no warranty. -# Notes : Requires watsup, wxPython - -from wxPython.wx import * -from wxPython.lib.anchors import LayoutAnchors - -from watsup.tools.showWindows import findAll,findNew,readPickle -from watsup.winGuiAuto import dumpWindow -from watsup.utils import tupleHwnd -import pprint - -def create(parent): - return ShowWindows(parent) - -[wxID_SHOWWINDOWS, wxID_SHOWWINDOWSFINDNEW, wxID_SHOWWINDOWSNEWWINDOWS, - wxID_SHOWWINDOWSPANEL1, wxID_SHOWWINDOWSREGISTER, wxID_SHOWWINDOWSREGISTERED, - wxID_SHOWWINDOWSTEXT, -] = map(lambda _init_ctrls: wxNewId(), range(7)) - -class ShowWindows(wxFrame): - def _init_ctrls(self, prnt): - # generated method, don't edit - wxFrame.__init__(self, id=wxID_SHOWWINDOWS, name='ShowWindows', - parent=prnt, pos=wxPoint(424, 184), size=wxSize(456, 433), - style=wxMINIMIZE_BOX | wxSTATIC_BORDER | wxCAPTION | wxSYSTEM_MENU, - title='ShowWindows 1.0') - self.SetClientSize(wxSize(448, 399)) - self.SetToolTipString('ShowWindow') - self.Center(wxBOTH) - self.Enable(True) - self.SetSizeHints(-1, -1, -1, -1) - self.SetThemeEnabled(False) - - self.panel1 = wxPanel(id=wxID_SHOWWINDOWSPANEL1, name='panel1', - parent=self, pos=wxPoint(0, 350), size=wxSize(450, 50), - style=wxTAB_TRAVERSAL) - self.panel1.SetConstraints(LayoutAnchors(self.panel1, True, True, False, - False)) - - self.Register = wxButton(id=wxID_SHOWWINDOWSREGISTER, label='Register', - name='Register', parent=self.panel1, pos=wxPoint(32, 13), - size=wxSize(75, 23), style=0) - self.Register.SetToolTipString('Register all windows info') - EVT_BUTTON(self.Register, wxID_SHOWWINDOWSREGISTER, - self.OnRegisterButton) - - self.FindNew = wxButton(id=wxID_SHOWWINDOWSFINDNEW, label='Find New', - name='FindNew', parent=self.panel1, pos=wxPoint(304, 13), - size=wxSize(75, 23), style=0) - EVT_BUTTON(self.FindNew, wxID_SHOWWINDOWSFINDNEW, self.OnFindNewButton) - - self.Text = wxTextCtrl(id=wxID_SHOWWINDOWSTEXT, name='Text', - parent=self, pos=wxPoint(0, 0), size=wxSize(450, 350), - style=wxRAISED_BORDER | wxTE_WORDWRAP | wxTE_MULTILINE, value='') - - self.Registered = wxTextCtrl(id=wxID_SHOWWINDOWSREGISTERED, - name='Registered', parent=self.panel1, pos=wxPoint(110, 16), - size=wxSize(40, 16), style=wxTE_CENTER | wxTE_READONLY, value='') - self.Registered.SetToolTipString('No of windows registered on system') - self.Registered.SetBackgroundColour(wxColour(175, 175, 175)) - - self.NewWindows = wxTextCtrl(id=wxID_SHOWWINDOWSNEWWINDOWS, - name='NewWindows', parent=self.panel1, pos=wxPoint(382, 16), - size=wxSize(40, 16), style=wxTE_CENTER | wxTE_READONLY, value='') - self.NewWindows.SetToolTipString('No of new windows found') - self.NewWindows.SetBackgroundColour(wxColour(175, 175, 175)) - - def __init__(self, parent): - self._init_ctrls(parent) - #load up the last value for your info - ws=readPickle() - if ws: - self.Registered.SetValue(str(len(ws))) - - def OnRegisterButton(self, event): - ws=findAll() - self.Registered.SetBackgroundColour(wxColour(255, 255, 255)) - self.Registered.SetValue(str(len(ws))) - - def OnFindNewButton(self, event): - ws=findNew() - self.NewWindows.SetBackgroundColour(wxColour(255, 255, 255)) - # write the details to the text box - withControls=self.writeNewWindows(ws) - self.NewWindows.SetValue('%s/%d' % (withControls,len(ws))) - - def writeNewWindows(self,ws): - noControls=0 - withControls=0 - txt=[] - for w in ws: - - dw=dumpWindow(w) - if not dw: - noControls+=1 - continue - - t=tupleHwnd(w) - # don't bother with ShowWindows application: - - wclass=t[2] - wtext=t[1] - if wclass=='wxWindowClass' and wtext.startswith('ShowWindows'): - noControls+=1 - continue - - if wtext: - wtext="%s" % wtext - else: - wtext='' - - withControls+=1 - # write the heading window - #txt.append('%d. %s %s' % (withControls,wclass,wtext)) - txt.append('%d. %s' % (withControls,str(list(t)))) - txt.append(pprint.pformat(dw)) - txt.append('') - - self.Text.SetValue('\n'.join(txt)) - - return withControls - diff --git a/agent/crawl/watsup/tools/runTests.ini b/agent/crawl/watsup/tools/runTests.ini deleted file mode 100644 index a528579..0000000 --- a/agent/crawl/watsup/tools/runTests.ini +++ /dev/null @@ -1,4 +0,0 @@ -[main] -checking = True -current_directory = C:\Python23\Lib\site-packages\watsup\examples\unittests - diff --git a/agent/crawl/watsup/tools/showWindows.py b/agent/crawl/watsup/tools/showWindows.py deleted file mode 100644 index 1aafe9d..0000000 --- a/agent/crawl/watsup/tools/showWindows.py +++ /dev/null @@ -1,34 +0,0 @@ -# logic for wxShowWindows - -# Author : Tim Couper - timc@tizmoi.net -# Date : 1 August 2004 -# Copyright : Copyright TAC Software Ltd, under Python-like licence. -# Provided as-is, with no warranty. -# Notes : Requires watsup - -import cPickle -from watsup.AppControls import findNewTopWindows -from watsup.winGuiAuto import dumpWindow -from watsup.utils import dumpHwnd -import os.path -from types import ListType - -PICKLE_FILE='findall.pkl' - -def readPickle(pickle_file=PICKLE_FILE): - #reads the list in the pickle file if possible - if os.path.exists(pickle_file): - return cPickle.load(open(pickle_file)) - else: - return [] - -def findAll(pickle_file=PICKLE_FILE): - # get all the top windows: - res=findNewTopWindows() - cPickle.dump(res,open(pickle_file,'w')) - return res - -def findNew(pickle_file=PICKLE_FILE): - # get all the top windows, and return any new ones - olds=readPickle(pickle_file) - return findNewTopWindows(olds) diff --git a/agent/crawl/watsup/tools/watsup_framework.bat b/agent/crawl/watsup/tools/watsup_framework.bat deleted file mode 100644 index 2ffdf75..0000000 --- a/agent/crawl/watsup/tools/watsup_framework.bat +++ /dev/null @@ -1 +0,0 @@ -python watsup_framework.py \ No newline at end of file diff --git a/agent/crawl/watsup/tools/watsup_framework.py b/agent/crawl/watsup/tools/watsup_framework.py deleted file mode 100644 index f6ff39f..0000000 --- a/agent/crawl/watsup/tools/watsup_framework.py +++ /dev/null @@ -1,653 +0,0 @@ - -# Author : Tim Couper - timc@tizmoi.net -# Date : 1 August 2004 -# Copyright : Copyright TAC Software Ltd, under Python-like licence. -# Provided as-is, with no warranty. -# Notes : Requires wxPython,watsup -# Based heavily on unittestgui.py by Chris Liechti - -import unittest, os, imp, time, traceback,sys - -## import all of the wxPython GUI package -from wxPython.wx import * -from wxPython.grid import * - -from watsup.performance import doChecking,onOff,isChecking -from watsup.tools.buildSuite import buildSuite -from time import ctime - -BASE_TITLE="Watsup Framework" -INI_FILE='./runTests.ini' - -from ConfigParser import ConfigParser,NoOptionError, NoSectionError - -#specify editor, make it return immediately, otherwise the GUI will wait -#until its closed. on win use "start cmd", on un*x "cmd&" -EDITOR = r'start c:\tools\wscite\scite "%(filename)s" -goto:%(lineno)s' -def starteditor(filename, lineno=1): - if os.path.exists(filename): - os.system(EDITOR % {'filename':filename, 'lineno':lineno}) - else: - wxMessageBox("Cannot locate sourcefile:\n%s" % filename, "Can't start editor...", wxOK) - -#---------------------------------------------------------------- - -class GUITestResult(unittest.TestResult): - """A test result class that can print formatted text results to a stream. - """ - separator1 = '=' * 70 - separator2 = '-' * 70 - - def __init__(self, listview, progress, descriptions, verbosity): - unittest.TestResult.__init__(self) - self.listview = listview - self.showAll = verbosity > 1 - self.descriptions = descriptions - self.progress = progress - self.testdescr = None - self.stream = sys.stdout - - def getDescription(self, test): - if self.descriptions: - return test.shortDescription() or str(test) - else: - return str(test) - - def startTest(self, test): - unittest.TestResult.startTest(self, test) - self.testdescr = self.getDescription(test) - if self.showAll: - self.stream.write(self.getDescription(test)) - self.stream.write(" ... ") - - def _correctFilename(self, filename): - if filename[-4:] in ('.pyc', '.pyo'): - return filename[:-1] - return filename - - def addSuccess(self, test): - unittest.TestResult.addSuccess(self, test) - if self.showAll: - self.stream.write("ok\n") - self.listview.Append( ( - 'Ok', - self.testdescr, - '', - None, - None - ) ) - self.progress.tick() - - def addError(self, test, err): - unittest.TestResult.addError(self, test, err) - if self.showAll: - self.stream.write("ERROR\n") - try: - filename = self._correctFilename(err[2].tb_frame.f_globals['__file__']) - except KeyError: - filename = None - lineno = err[2].tb_lineno - self.listview.Append( ( - 'Error', - self.testdescr, - traceback.format_exception(*err)[-1].rstrip(), - self._exc_info_to_string(err), - (filename, lineno) - ) ) - self.progress.tick() - - def addFailure(self, test, err): - unittest.TestResult.addFailure(self, test, err) - if self.showAll: - self.stream.write("FAIL\n") - try: - filename = self._correctFilename(err[2].tb_frame.f_globals['__file__']) - except KeyError: - filename = None - lineno = err[2].tb_lineno - self.listview.Append( ( - 'Fail', - self.testdescr, - traceback.format_exception(*err)[-1].rstrip(), - self._exc_info_to_string(err), - (filename, lineno) - ) ) - self.progress.tick() - - def printErrors(self): - if self.showAll: - self.stream.write("\n") - self.printErrorList('ERROR', self.errors) - self.printErrorList('FAIL', self.failures) - - def printErrorList(self, flavour, errors): - for test, err in errors: - self.stream.write(self.separator1) - self.stream.write("\n%s: %s\n" % (flavour,self.getDescription(test))) - self.stream.write(self.separator2) - self.stream.write("\n%s\n" % err) - -class GUITestRunner: - """A test runner class that displays results in textual form. - - It prints out the names of tests as they are run, errors as they - occur, and a summary of the results at the end of the test run. - """ - def __init__(self, listview, progress, stream=sys.stderr, descriptions=1, verbosity=2): - self.listview = listview - self.progress = progress - self.stream = unittest._WritelnDecorator(stream) - self.descriptions = descriptions - self.verbosity = verbosity - - def _makeResult(self): - return GUITestResult(self.listview, self.progress, self.descriptions, self.verbosity) - - def run(self, test): - "Run the given test case or test suite." - result = self._makeResult() - startTime = time.time() - test(result) - stopTime = time.time() - timeTaken = float(stopTime - startTime) - result.printErrors() - self.stream.writeln(result.separator2) - run = result.testsRun - self.stream.writeln("Ran %d test%s in %.3fs" % - (run, run == 1 and "" or "s", timeTaken)) - self.stream.writeln() - if not result.wasSuccessful(): - self.stream.write("FAILED (") - failed, errored = map(len, (result.failures, result.errors)) - if failed: - self.stream.write("failures=%d" % failed) - if errored: - if failed: self.stream.write(", ") - self.stream.write("errors=%d" % errored) - self.stream.writeln(")") - else: - self.stream.writeln("OK") - - return result - -#---------------------------------------------------------------- - -def lastline(t): - t = t.rstrip() - n = t.rfind('\n') - if n >= 0: - return t[n+1:] - return t - -#---------------------------------------------------------------- - -class ResultView(wxListCtrl): - PM_DETAIL = wxNewId() - PM_LOCATE = wxNewId() - - def __init__(self, parent): - wxListCtrl.__init__(self, parent, -1, - style=wxLC_REPORT|wxSUNKEN_BORDER|wxLC_VIRTUAL|wxLC_VRULES,#|wxLC_HRULES, - size=(500,200)) - - self.InsertColumn(0, '-Ok-') - self.InsertColumn(1, 'Description') - self.InsertColumn(2, 'Details') - - self.SetColumnWidth(0, 40) - w = self.GetSize()[0] - self.GetColumnWidth(0) - self.SetColumnWidth(1, w/2) - self.SetColumnWidth(2, w/2) - - EVT_RIGHT_DOWN(self, self.OnRightClick) - EVT_LIST_ITEM_SELECTED(self, self.GetId(), self.OnItemSelected) - EVT_LIST_ITEM_ACTIVATED(self, self.GetId(), self.OnItemActivated) - - self.red = wxListItemAttr() - self.red.SetBackgroundColour(wxRED) - self.green = wxListItemAttr() - self.green.SetBackgroundColour(wxColour(200,255,200)) #light green - self.orange = wxListItemAttr() - self.orange.SetBackgroundColour(wxColour(255,200,200)) #light red - self.result = [] - self.currentItem = None - - EVT_SIZE(self, self.OnSize) - - #node menu - self.nodemenu = wxMenu() - self.nodemenu.Append(self.PM_DETAIL, "Show Details") - #self.nodemenu.AppendSeparator() - #self.nodemenu.Append(self.PM_LOCATE, "Locate Source") - - EVT_MENU(self, self.PM_DETAIL, self.OnDetail) - #EVT_MENU(self, self.PM_LOCATE, self.OnLocate) - - def OnDetail(self, event=None): - if self.result[self.currentItem][3]: - wxMessageBox(self.result[self.currentItem][3], "Result details", wxOK) - - def OnLocate(self, event=None): - filename, lineno = self.result[self.currentItem][4] - print "locate",filename, lineno - starteditor(filename, lineno) - - def OnSize(self, event): - w = self.GetSize()[0] - self.GetColumnWidth(0) - 30 - if w < 180: w = 180 - self.SetColumnWidth(1, w/2) - self.SetColumnWidth(2, w/2) - - def Append(self, item): - self.result.append(item) - self.SetItemCount(len(self.result)) - #wxYield() - #self.Refresh() - - def DeleteAllItems(self): - self.result = [] - wxListCtrl.DeleteAllItems(self) - - def OnItemSelected(self, event): - self.currentItem = event.m_itemIndex - - def OnItemActivated(self, event): - self.currentItem = event.m_itemIndex - self.OnDetail() - - def OnRightClick(self, event): - pt = event.GetPosition() - item, flags = self.HitTest(pt) - if not flags & wxLIST_HITTEST_NOWHERE: - if self.currentItem is not None: - self.SetItemState(self.currentItem, 0, wxLIST_STATE_SELECTED ) - self.currentItem = item - self.SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED ) - if self.result[self.currentItem][3]: - self.PopupMenu(self.nodemenu, pt) #display popup menu - - def OnCopyAsText(self, all=0): - res = [] - item = -1; - while 1: - if all: - item = self.GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE); - else: - item = self.GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - text = '\t'.join(map(str, self.result[item])) - res.append(text) - if item == -1: - break - clip = wxClipboard() - clip.Open() - clip.SetData( wxTextDataObject('\n'.join(res)) ) - clip.Close() - - #--------------------------------------------------- - # These methods are callbacks for implementing the - # "virtualness" of the list... - def OnGetItemText(self, row, col): - #print row, col - try: - return str(self.result[row][col]) - except IndexError: - return '' - - def OnGetItemImage(self, item): - return 0 - - def OnGetItemAttr(self, item): - if self.result[item][0] == 'Error': - return self.red - elif self.result[item][0] == 'Fail': - return self.orange - elif self.result[item][0] == 'Ok': - return self.green - return None - -class TreeView(wxTreeCtrl): - PM_RUNSEL = wxNewId() - PM_LOCATE = wxNewId() - - def __init__(self, *args, **kwargs): - wxTreeCtrl.__init__(self, *args, **kwargs) - EVT_LEFT_DCLICK(self, self.OnLeftDClick) - EVT_RIGHT_DOWN(self, self.OnRightClick) - EVT_RIGHT_UP(self, self.OnRightUp) - EVT_TREE_BEGIN_LABEL_EDIT(self, self.GetId(), self.OnBeginEdit) - EVT_TREE_END_LABEL_EDIT (self, self.GetId(), self.OnEndEdit) - - #node menu -## self.nodemenu = wxMenu() -## self.nodemenu.Append(self.PM_RUNSEL, "Run selected") -## self.nodemenu.AppendSeparator() -## self.nodemenu.Append(self.PM_LOCATE, "Locate Source") -## -## EVT_MENU(self, self.PM_RUNSEL, self.OnRunSel) -## EVT_MENU(self, self.PM_LOCATE, self.OnLocate) - - #don't allow editing of node names - def OnBeginEdit(self, event): event.Veto() - def OnEndEdit(self, event): event.Veto() - - def OnLeftDClick(self, event): -## pt = event.GetPosition(); -## item, flags = self.HitTest(pt) -## #print("OnLeftDClick: %s" % self.GetItemText(item)) -## parent = self.GetItemParent(item) -## #self.SortChildren(parent) - event.Skip() - - def OnRightClick(self, event): - pt = event.GetPosition() - item, flags = self.HitTest(pt) - if not flags & wxTREE_HITTEST_NOWHERE: - self.SelectItem(item) - #self.PopupMenu(self.nodemenu, pt) #display node menu - - def OnRightUp(self, event): - pt = event.GetPosition(); - item, flags = self.HitTest(pt) - #self.tree.EditLabel(item) - - def OnCompareItems(self, item1, item2): - t1 = self.GetItemText(item1) - t2 = self.GetItemText(item2) - #print('compare: ' + t1 + ' <> ' + t2) - return cmp(t1,t2) - - def OnRunSel(self, event): - pass - def OnLocate(self, event): - pass - -#---------------------------------------------------------------- - -class ProgressBar(wxGauge): - def __init__(self, *args, **kwargs): - wxGauge.__init__(self, *args, **kwargs) - self.SetBezelFace(5) - self.SetShadowWidth(5) - self.increment = 1 - - def tick(self): - self.SetValue(self.GetValue() + self.increment) - -#---------------------------------------------------------------- - -class Frame(wxFrame): - ID_BUTTON = wxNewId() - #M_NEW = wxNewId() - M_OPENFILES = wxNewId() - M_OPENDIR = wxNewId() - M_OPENTREE = wxNewId() - M_EXIT = wxNewId() - M_COPYALL = wxNewId() - M_COPYSEL = wxNewId() - M_RUN = wxNewId() - M_CHECKING = wxNewId() - - - def __init__(self, parent, id, title = BASE_TITLE): - - # First, call the base class' __init__ method to create the frame - wxFrame.__init__(self, parent, id, title, wxPoint(100, 100), wxSize(100, 100)) - #variables - self.suite = None - - self.loadConfig() - - #menu - menuBar = wxMenuBar() - menu = wxMenu() - #menu.Append(self.M_NEW, "&New") - menu.Append(self.M_OPENFILES, "&Load Test File(s)") - menu.Append(self.M_OPENDIR, "&Load Test Directory") - menu.Append(self.M_OPENTREE, "&Load Test Directory Tree") - menu.AppendSeparator() - menu.Append(self.M_EXIT, "E&xit") - #EVT_MENU(self, self.M_NEW, self.OnNew) - EVT_MENU(self, self.M_OPENFILES, self.OnMenuOpenFiles) - EVT_MENU(self, self.M_OPENDIR, self.OnMenuOpenDir) - EVT_MENU(self, self.M_OPENTREE, self.OnMenuOpenTree) - EVT_MENU(self, self.M_EXIT, self.OnCloseWindow) - menuBar.Append(menu, "&File"); - - menu = wxMenu() - menu.Append(self.M_COPYALL, "&Copy all results") - menu.Append(self.M_COPYSEL, "&Copy selected results") - EVT_MENU(self, self.M_COPYALL, self.OnCopyAll) - EVT_MENU(self, self.M_COPYSEL, self.OnCopySelection) - menuBar.Append(menu, "&Edit"); - - menu = wxMenu() - menu.Append(self.M_RUN, "&Run all") - menu.AppendSeparator() - menu.AppendCheckItem(self.M_CHECKING,"&Checking") - menu.Check(self.M_CHECKING,isChecking()) - EVT_MENU(self, self.M_RUN, self.OnRun) - EVT_MENU(self, self.M_CHECKING, self.OnChecking) - self.testing_menu=menu - menuBar.Append(menu, "&Testing") - - self.SetMenuBar(menuBar) - - #GUI - panel = wxPanel(self, 0) - - button = wxButton(panel, self.ID_BUTTON, "Run Tests" ) - button.SetDefault() - EVT_BUTTON(panel, self.ID_BUTTON, self.OnRun ) - - self.tree = TreeView(panel, -1, size=(300,250), style=wxTR_HAS_BUTTONS | wxTR_EDIT_LABELS)# | wxTR_MULTIPLE - - self.progress = ProgressBar(panel, -1, 100, style=wxGA_HORIZONTAL|wxGA_SMOOTH) - - self.list = ResultView(panel) - - sizer = wxBoxSizer(wxVERTICAL) - sizer.Add(self.tree, 1, wxEXPAND) - sizer.Add(button, 0, wxEXPAND) - sizer.Add(self.progress, 0, wxEXPAND) - sizer.Add(self.list, 1, wxEXPAND) - - panel.SetAutoLayout(1) - panel.SetSizer(sizer) - sizer.Fit(panel) - - basesizer = wxBoxSizer(wxVERTICAL) - basesizer.Add(panel, 1, wxEXPAND) - self.SetAutoLayout(1) - self.SetSizer(basesizer) - self.Fit() - - #create a statusbar - sb = self.CreateStatusBar(1) - sb.SetStatusWidths([-1]) - self.SetStatusText('Please load file(s), a directory or tree',0) - #update controls - self.OnNew() - - def __del__(self): - # persist any values in .ini file - - cfg=ConfigParser() - cfg.add_section('main') - cfg.set('main','current_directory',self.getCurrentDirectory()) - cfg.set('main','checking',isChecking()) - cfg.write(open(INI_FILE,'w')) - - def loadConfig(self): - # load settings from the config file - - cfg=ConfigParser() - cfg.read(INI_FILE) - - self._currentDirectory='.' - try: - self.setCurrentDirectory(cfg.get('main','current_directory')) - except NoSectionError: # none of the other options will be available - return - except NoOptionError: - pass - - try: - doChecking(cfg.getboolean('main','checking')) - except (NoOptionError, NoSectionError): - doChecking(True) - - - def UpdateTree(self, root=None, testlist=None): - if root is None: - root = self.root - if testlist is None and self.suite: - testlist = self.suite._tests #grmpf accessing a _ member, oh well - - if root and testlist: - for testcase in testlist: - if isinstance(testcase, unittest.TestSuite): - - testname=testcase._tests[0] - #str(testname): atestcasename (module.class) - # we want module.class here, with no brackets - # this should do it - label= str(testname).split('(')[1] - label=label[:-1] - - child = self.tree.AppendItem(root, "%s (%d)" % (label,len(testcase._tests))) - self.tree.SetPyData(child, None) - self.UpdateTree(child, testcase._tests) - self.tree.SortChildren(child) - self.tree.Collapse(child) - else: - - label="%s" % testcase - label=label.split('(')[0] - - child = self.tree.AppendItem(root,label) - self.tree.SetPyData(child, None) - ##self.tree.SetItemImage(child, idx2, wxTreeItemIcon_Expanded) - ##self.tree.SetItemSelectedImage(child, idx3) - - self.tree.Expand(root) - - def OnRun(self, event=None): - """ """ - if self.suite: - runner = GUITestRunner(self.list, self.progress) - self.SetStatusText('Running tests...',0) - ln = self.suite.countTestCases() - self.progress.SetValue(0) - self.progress.SetRange(ln) - self.list.DeleteAllItems() - - result = runner.run(self.suite) - - self.SetStatusText('Ran %d tests, %d errors, %d failures' % (ln, len(result.errors), len(result.failures)), 0) - - else: - self.SetStatusText('No tests found', 0) - - - def OnMenuOpenFiles(self, event=None): - """ """ - - dlg = wxFileDialog(self, "Choose one or more modules", - self.getCurrentDirectory(),"", - 'Python files (*.py)|*.py|All files (*.*)|*.*', - wxOPEN|wxMULTIPLE) - - if dlg.ShowModal() == wxID_OK: -## for path in dlg.GetPaths(): -## log.WriteText('You selected: %s\n' % path) - self.OnNew() - filenames = dlg.GetPaths() #dlg.GetPath() - self.setCurrentDirectory(dlg.GetDirectory()) - self.suite=buildSuite(filenames) - - #print self.suite - dlg.Destroy() - self.UpdateTree() - - def OnMenuOpenDir(self,event=None): - self.loadDirectory('Choose the test directory') - - def OnMenuOpenTree(self,event=None): - self.loadDirectory('Choose the top test directory',True) - - def loadDirectory(self,msg,tree=False): - dlg=wxDirDialog(self,msg, self.getCurrentDirectory()) - if dlg.ShowModal() == wxID_OK: - self.OnNew() - dir = dlg.GetPath() - self.setCurrentDirectory(dir) - self.suite=buildSuite(dir,tree) - #print self.suite - dlg.Destroy() - self.UpdateTree() - - def OnNew(self, event=None): - self.list.DeleteAllItems() - self.tree.DeleteAllItems() - self.root = self.tree.AddRoot("Watsup Tests:") - self.tree.SetPyData(self.root, None) - self.progress.SetValue(0) - self.suite = None - #self.filename = None - - def OnCopySelection(self,event=None): - self.list.OnCopyAsText() - - def OnCopyAll(self,event=None): - self.list.OnCopyAsText(all=1) - - def OnCloseWindow(self, event=None): - self.Destroy() - - - def OnChecking(self,event=None): - # toggle self._checking: - bool=not isChecking() - self.testing_menu.Check(self.M_CHECKING,bool) - doChecking(bool) - self.SetStatusText('Checking %s' % onOff(bool),0) - - def getCurrentDirectory(self): - if os.path.isdir(self._currentDirectory): - return self._currentDirectory - else: - return '.' - - def setCurrentDirectory(self,value): - if self._currentDirectory<>value: - if os.path.isdir(value): - self._currentDirectory=value - # update the title - if value=='.': - text='' - else: - text='(%s)' % value - - self.SetTitle('%s %s'.strip() % (BASE_TITLE,text)) - else: - self._currentDirectory='.' - - -#--------------------------------------------------------------------------- -# Every wxWindows application must have a class derived from wxApp -class App(wxApp): - # wxWindows calls this method to initialize the application - def OnInit(self): - # Create an instance of our customized Frame class - frame = Frame(NULL, -1) - frame.Show(true) - # Tell wxWindows that this is our main window - self.SetTopWindow(frame) - # Return a success flag - return true - -#--------------------------------------------------------------------------- -if __name__ == "__main__": - app = App(0) # Create an instance of the application class - app.MainLoop() # Tell it to start processing events - diff --git a/agent/crawl/watsup/tools/wxShowWindows.py b/agent/crawl/watsup/tools/wxShowWindows.py deleted file mode 100644 index 2ec2110..0000000 --- a/agent/crawl/watsup/tools/wxShowWindows.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -#Boa:App:BoaApp - -# Author : Tim Couper - tim@tizmoi.net -# Date : 1 August 2004 -# Copyright : Copyright TAC Software Ltd, under Python-like licence. -# Provided as-is, with no warranty. -# Notes : Requires watsup,wxPython - -from wxPython.wx import * - -import fmShowWindows - -modules ={'fmShowWindows': [1, 'Main frame of Application', 'fmShowWindows.py']} - -class BoaApp(wxApp): - def OnInit(self): - wxInitAllImageHandlers() - self.main = fmShowWindows.create(None) - self.main.Show() - self.SetTopWindow(self.main) - return True - -def main(): - application = BoaApp(0) - application.MainLoop() - -if __name__ == '__main__': - main() diff --git a/agent/crawl/watsup/utils.py b/agent/crawl/watsup/utils.py deleted file mode 100644 index 79c502a..0000000 --- a/agent/crawl/watsup/utils.py +++ /dev/null @@ -1,37 +0,0 @@ -""" watsup system utilities -""" - -# Author : Tim Couper - timc@tizmoi.net -# Date : 22 July 2004 -# Version : 1.0 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. -# Notes : Requires win32all - -import win32gui -import win32api -import sys - -class WatsupError(Exception): pass - -##def kill(pid): - -## """kill function for Win32""" - -## handle = win32api.OpenProcess(1, 0, pid) - -## return (0 != win32api.TerminateProcess(handle, 0)) - -def dumpHwnd(hwnd): - t=list(tupleHwnd(hwnd)) - t.reverse() - return '%s:"%s" (%d)' % tuple(t) #(win32gui.GetClassName(hwnd),win32gui.GetWindowText(hwnd),hwnd) - -def tupleHwnd(hwnd): - return (hwnd,win32gui.GetWindowText(hwnd),win32gui.GetClassName(hwnd)) - -def pump(): - win32gui.PumpWaitingMessages() - -if __name__=='__main__': - pass \ No newline at end of file diff --git a/agent/crawl/watsup/wie.py b/agent/crawl/watsup/wie.py deleted file mode 100644 index 8f5cf37..0000000 --- a/agent/crawl/watsup/wie.py +++ /dev/null @@ -1,45 +0,0 @@ -# provides a testing interface to web applications via the PAMIE module - -# Author : Tim Couper - timc@tizmoi.net -# Date : 22 July 2004 -# Version : 1.0 -# Copyright : Copyright TAC Software Ltd, under Python-style licence. -# Provided as-is, with no warranty. - -from cPAMIE import PAMIE - -def findRunningIE(): - from win32com.client import Dispatch - from win32gui import GetClassName - - ShellWindowsCLSID = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}' - ShellWindows = Dispatch ( ShellWindowsCLSID ) - - # try to get an ie instance from the window - for shellwindow in ShellWindows : - if GetClassName ( shellwindow.HWND ) == 'IEFrame' : - return shellwindow - -class WatsupIE(PAMIE): - def __init__(self,url=None, timeOut=1000, useExistingIfPossible=False): - - self._ie=None - - if useExistingIfPossible: - self._ie=findRunningIE() - - if self._ie: - # this case can only arise if we've located a running IE; - - # the code below should be everything else in PAMIE.__init__, - # apart from instantiation of the new ie instance: - if url: - self._ie.Navigate(url) - else: - self._ie.Navigate('about:blank') - self._timeOut = timeOut - self._ie.Visible = 1 - else: - PAMIE.__init__(self,url,timeOut) - - \ No newline at end of file diff --git a/agent/crawl/watsup/winGuiAuto.py b/agent/crawl/watsup/winGuiAuto.py deleted file mode 100644 index 9ae3f4c..0000000 --- a/agent/crawl/watsup/winGuiAuto.py +++ /dev/null @@ -1,1169 +0,0 @@ -# Module : winGuiAuto.py -# Synopsis : Windows GUI automation utilities -# Programmer : Simon Brunning - simon@brunningonline.net -# Date : 25 June 2003 -# Version : 1.0 -# Copyright : Released to the public domain. Provided as-is, with no warranty. -# Notes : Requires Python 2.3, win32all and ctypes - -# Modifications by Tim Couper - tim@tizmoi.net -# 22 Jul 2004 -# findControls: deduplicates the list to be returned -# findControl: handles win32gui.error from initial call to findControls -# getMenuInfo: improved algorithm for calculating no. of items in a menu -# activateMenuItem: improved algorithm for calculating no. of items in a menu -# -# GLOBALLY corrected spelling: seperator -> separator -# : descendent -> descendant -# added findMenuItem - -'''Windows GUI automation utilities. - -TODO - Until I get around to writing some docs and examples, the tests at the -foot of this module should serve to get you started. - -The standard pattern of usage of winGuiAuto is in three stages; identify a -window, identify a control in the window, trigger an action on the control. - -The end result is always that you wish to have an effect upon some Windows GUI -control. - -The first stage is to identify the window within which the control can be -found. To do this, you can use either findTopWindow or findTopWindows. The -findTopWindow function returns a reference to single window, or throws an -exception if multiple windows or no windows matching the supplied selection -criteria are found. If no matching window is found, immediately, the -findTopWindow function may keep trying to find a match, depending upon suppled -retry aguments. The findTopWindows function returns a list of references to all -windows matching the supplied selection criteria; this list may be empty. The -findTopWindows function will return immediately. - -Usually, specifying caption text, the window's class, or both, will be -sufficient to identify the required window. (Note that the window's class is a -Windows API concept; it has nothing to do with Python classes. See http://msdn.microsoft.com/library/en-us/winui/WinUI/WindowsUserInterface/Windowing/Window_89WindowClasse.asp -for an overview.) Caption text will match if the specified text is *contained* -in the windows' captions; this match is case unsensitive. Window class names -must match identically; the full class name must be matched, and the test is -case sensitive. - -This matching behavior is usually the most useful - but not always. If other -selection behavior is required, a selection function can be passed to the find -functions. - -These returned references are in the form of 'Windows Handles'. All windows and -controls are accessed via a Windows Handle (or hwnd). See -http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/windowing/windows/aboutwindows.asp -for Microsoft's info-deluge on all things Windows window. - -Having identified the window, the next stage is to identify the control. To do -this, you can use either of the findControl and findControls functions. These -work in almost exactly the same way as the findTopWindow and findTopWindows -functions; they take the hwnd of the top level window within which the required -control should be found, and otherwise take the same arguments as the -findTopWindow and findTopWindows functions, and work in the same ways. - -Having now identified your control, you can now query its state, and send -actions (in the form of Windows messages) to it. It's at this point that you -are most likely to end up having to extend winGuiAuto in some way; the -developer of the GUI that you are trying to drive is free to develop controls -which respond to different messages in different ways. Still, if standard MFC -controls have been used, you may find that winGuiAuto's functions may provide -much or all of what you need - or at least provide a useful template. These -functions (clickButton, clickStatic, doubleClickStatic, getComboboxItems, -getEditText, getListboxItems, selectComboboxItem, selectListboxItem and -setEditText) should be fairly self evident. - -Selection of menu items is a slightly different process: but don't fret; it's -pretty simple. You'll need to identify the hwnd of the top level window that -you want to control using either of the findTopWindow and findTopWindows -functions. You can then call the activateMenuItem function, passing in the top -level window's hwnd and the path to the menu option that you wish to activate. -This path should consist of a list specifiying the path to the required menu -option - see activateMenuItem's docstring for details. - -TODO getMenuInfo - -TODO: dumpTopWindows, dumpWindow, Spy++ and Winspector (http://www.windows-spy.com) -Two if the main difficulties you'll come across while using winGuiAuto will be discovering - the classes of the windows that you want, and where the provided -''' - -import array -import ctypes -import os -import struct -import sys -import time -import win32api -import win32con -import win32gui - -def findTopWindow(wantedText=None, - wantedClass=None, - selectionFunction=None, - maxWait=1, - retryInterval=0.1): - '''Find the hwnd of a top level window. - - You can identify windows using captions, classes, a custom selection - function, or any combination of these. (Multiple selection criteria are - ANDed. If this isn't what's wanted, use a selection function.) - - If no window matching the specified selection criteria is found - immediately, further attempts will be made. The retry interval and maximum - time to wait for a matching window can be specified. - - Arguments: - - wantedText Text which the required window's caption must - *contain*. (Case insensitive match.) - wantedClass Class to which the required window must belong. - selectionFunction Window selection function. Reference to a function - should be passed here. The function should take hwnd - as an argument, and should return True when passed the - hwnd of a desired window. - maxWait The maximum time to wait for a matching window, in - seconds. - retryInterval How frequently to look for a matching window, in - seconds - - Raises: - WinGuiAutoError When no window or multiple windows found. - - Usage examples: # Caption contains "Options" - optDialog = findTopWindow(wantedText="Options") - # Caption *equals* "Notepad" - saveDialog = findTopWindow(selectionFunction=lambda hwnd: - win32gui.GetWindowText(hwnd)=='Notepad') - ''' - topWindows = findTopWindows(wantedText, wantedClass, selectionFunction) - if len(topWindows) > 1: - raise WinGuiAutoError("Multiple top level windows found for wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - elif topWindows: - return topWindows[0] - elif (maxWait-retryInterval) >= 0: - time.sleep(retryInterval) - try: - return findTopWindow(wantedText=wantedText, - wantedClass=wantedClass, - selectionFunction=selectionFunction, - maxWait=maxWait-retryInterval, - retryInterval=retryInterval) - except WinGuiAutoError: - raise WinGuiAutoError("No top level window found for wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - else: - raise WinGuiAutoError("No top level window found for wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - -def findTopWindows(wantedText=None, wantedClass=None, selectionFunction=None): - '''Find the hwnd of top level windows. - - You can identify windows using captions, classes, a custom selection - function, or any combination of these. (Multiple selection criteria are - ANDed. If this isn't what's wanted, use a selection function.) - - Arguments: - wantedText Text which required windows' captions must contain. - wantedClass Class to which required windows must belong. - selectionFunction Window selection function. Reference to a function - should be passed here. The function should take hwnd as - an argument, and should return True when passed the - hwnd of a desired window. - - Returns: A list containing the window handles of all top level - windows matching the supplied selection criteria. - - Usage example: optDialogs = findTopWindows(wantedText="Options") - ''' - results = [] - topWindows = [] - win32gui.EnumWindows(_windowEnumerationHandler, topWindows) - for hwnd, windowText, windowClass in topWindows: - if wantedText and \ - not _normaliseText(wantedText) in _normaliseText(windowText): - continue - if wantedClass and not windowClass == wantedClass: - continue - if selectionFunction and not selectionFunction(hwnd): - continue - results.append(hwnd) - return results - -def dumpTopWindows(): - '''TODO''' - topWindows = [] - win32gui.EnumWindows(_windowEnumerationHandler, topWindows) - return topWindows - -def dumpWindow(hwnd): - '''Dump all controls from a window into a nested list - - Useful during development, allowing to you discover the structure of the - contents of a window, showing the text and class of all contained controls. - - Think of it as a poor man's Spy++. ;-) - - Arguments: The window handle of the top level window to dump. - - Returns A nested list of controls. Each entry consists of the - control's hwnd, its text, its class, and its sub-controls, - if any. - - Usage example: replaceDialog = findTopWindow(wantedText='Replace') - pprint.pprint(dumpWindow(replaceDialog)) - ''' - windows = [] - try: - win32gui.EnumChildWindows(hwnd, _windowEnumerationHandler, windows) - except win32gui.error: - # No child windows - return - windows = [list(window) for window in windows] - for window in windows: - childHwnd, windowText, windowClass = window - window_content = dumpWindow(childHwnd) - if window_content: - window.append(window_content) - - def dedup(thelist): - '''De-duplicate deeply nested windows list.''' - def listContainsSublists(thelist): - return bool([sublist - for sublist in thelist - if isinstance(sublist, list)]) - found=[] - def dodedup(thelist): - todel = [] - for index, thing in enumerate(thelist): - if isinstance(thing, list) and listContainsSublists(thing): - dodedup(thing) - else: - if thing in found: - todel.append(index) - else: - found.append(thing) - todel.reverse() - for todel in todel: - del thelist[todel] - dodedup(thelist) - dedup(windows) - - return windows - -def findControl(topHwnd, - wantedText=None, - wantedClass=None, - selectionFunction=None, - maxWait=1, - retryInterval=0.1): - '''Find a control. - - You can identify a control within a top level window, using caption, class, - a custom selection function, or any combination of these. (Multiple - selection criteria are ANDed. If this isn't what's wanted, use a selection - function.) - - If no control matching the specified selection criteria is found - immediately, further attempts will be made. The retry interval and maximum - time to wait for a matching control can be specified. - - Arguments: - topHwnd The window handle of the top level window in which the - required controls reside. - wantedText Text which the required control's captions must contain. - wantedClass Class to which the required control must belong. - selectionFunction Control selection function. Reference to a function - should be passed here. The function should take hwnd as - an argument, and should return True when passed the - hwnd of the desired control. - maxWait The maximum time to wait for a matching control, in - seconds. - retryInterval How frequently to look for a matching control, in - seconds - - Returns: The window handle of the first control matching the - supplied selection criteria. - - Raises: - WinGuiAutoError When no control or multiple controls found. - - Usage example: optDialog = findTopWindow(wantedText="Options") - okButton = findControl(optDialog, - wantedClass="Button", - wantedText="OK") - ''' - controls = findControls(topHwnd, - wantedText=wantedText, - wantedClass=wantedClass, - selectionFunction=selectionFunction) - # check for None returned: Tim 6 Jul 2004 - if controls==None: - raise WinGuiAutoError("EnumChildWindows failed with win32gui.error " + - repr(topHwnd) + - ", wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval) - ) - - if len(controls) > 1: - raise WinGuiAutoError("Multiple controls found for topHwnd=" + - repr(topHwnd) + - ", wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - elif controls: - return controls[0] - elif (maxWait-retryInterval) >= 0: - time.sleep(retryInterval) - try: - return findControl(topHwnd=topHwnd, - wantedText=wantedText, - wantedClass=wantedClass, - selectionFunction=selectionFunction, - maxWait=maxWait-retryInterval, - retryInterval=retryInterval) - except WinGuiAutoError: - raise WinGuiAutoError("No control found for topHwnd=" + - repr(topHwnd) + - ", wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - else: - raise WinGuiAutoError("No control found for topHwnd=" + - repr(topHwnd) + - ", wantedText=" + - repr(wantedText) + - ", wantedClass=" + - repr(wantedClass) + - ", selectionFunction=" + - repr(selectionFunction) + - ", maxWait=" + - repr(maxWait) + - ", retryInterval=" + - str(retryInterval)) - -def findControls(topHwnd, - wantedText=None, - wantedClass=None, - selectionFunction=None): - '''Find controls. - - You can identify controls using captions, classes, a custom selection - function, or any combination of these. (Multiple selection criteria are - ANDed. If this isn't what's wanted, use a selection function.) - - Arguments: - topHwnd The window handle of the top level window in which the - required controls reside. - wantedText Text which the required controls' captions must contain. - wantedClass Class to which the required controls must belong. - selectionFunction Control selection function. Reference to a function - should be passed here. The function should take hwnd as - an argument, and should return True when passed the - hwnd of a desired control. - - Returns: The window handles of the controls matching the - supplied selection criteria. - - Usage example: optDialog = findTopWindow(wantedText="Options") - def findButtons(hwnd, windowText, windowClass): - return windowClass == "Button" - buttons = findControl(optDialog, wantedText="Button") - ''' - def searchChildWindows(currentHwnd): - results = [] - childWindows = [] - try: - win32gui.EnumChildWindows(currentHwnd, - _windowEnumerationHandler, - childWindows) - except win32gui.error: - # This seems to mean that the control *cannot* have child windows, - # i.e. is not a container. - return - - for childHwnd, windowText, windowClass in childWindows: - descendantMatchingHwnds = searchChildWindows(childHwnd) - if descendantMatchingHwnds: - results += descendantMatchingHwnds - - if wantedText and \ - not _normaliseText(wantedText) in _normaliseText(windowText): - continue - if wantedClass and \ - not windowClass == wantedClass: - continue - if selectionFunction and \ - not selectionFunction(childHwnd): - continue - results.append(childHwnd) - return results - - # deduplicate the returned windows: Tim 6 Jul 2004 - #return searchChildWindows(topHwnd) - - hlist=searchChildWindows(topHwnd) - - if hlist: - # deduplicate the list: - hdict={} - for h in hlist: - hdict[h]='' - return hdict.keys() - else: - return hlist - -def getTopMenu(hWnd): - '''Get a window's main, top level menu. - - Arguments: - hWnd The window handle of the top level window for which the top - level menu is required. - - Returns: The menu handle of the window's main, top level menu. - - Usage example: hMenu = getTopMenu(hWnd) - ''' - return ctypes.windll.user32.GetMenu(ctypes.c_long(hWnd)) - -def activateMenuItem(hWnd, menuItemPath): - '''Activate a menu item - - Arguments: - hWnd The window handle of the top level window whose menu you - wish to activate. - menuItemPath The path to the required menu item. This should be a - sequence specifying the path through the menu to the - required item. Each item in this path can be specified - either as an index, or as a menu name. - - Raises: - WinGuiAutoError When the requested menu option isn't found. - - Usage example: activateMenuItem(notepadWindow, ('file', 'open')) - - Which is exactly equivalent to... - - activateMenuItem(notepadWindow, (0, 1)) - ''' - # By Axel Kowald (kowald@molgen.mpg.de) - # Modified by S Brunning to accept strings in addition to indicies. - - # Top level menu - hMenu = getTopMenu(hWnd) - - # Get top level menu's item count. Is there a better way to do this? - # Yes .. Tim Couper 22 Jul 2004 - - hMenuItemCount=win32gui.GetMenuItemCount(hMenu) - -## for hMenuItemCount in xrange(256): -## try: -## _getMenuInfo(hMenu, hMenuItemCount) -## except WinGuiAutoError: -## break -## hMenuItemCount -= 1 - - # Walk down submenus - for submenu in menuItemPath[:-1]: - try: # submenu is an index - 0 + submenu - submenuInfo = _getMenuInfo(hMenu, submenu) - hMenu, hMenuItemCount = submenuInfo.submenu, submenuInfo.itemCount - except TypeError: # Hopefully, submenu is a menu name - try: - dump, hMenu, hMenuItemCount = _findNamedSubmenu(hMenu, - hMenuItemCount, - submenu) - except WinGuiAutoError: - raise WinGuiAutoError("Menu path " + - repr(menuItemPath) + - " cannot be found.") - - # Get required menu item's ID. (the one at the end). - menuItem = menuItemPath[-1] - try: # menuItem is an index - 0 + menuItem - menuItemID = ctypes.windll.user32.GetMenuItemID(hMenu, - menuItem) - except TypeError: # Hopefully, menuItem is a menu name - try: - subMenuIndex, dump, dump = _findNamedSubmenu(hMenu, - hMenuItemCount, - menuItem) - except WinGuiAutoError: - raise WinGuiAutoError("Menu path " + - repr(menuItemPath) + - " cannot be found.") - menuItemID = ctypes.windll.user32.GetMenuItemID(hMenu, subMenuIndex) - - # Activate - win32gui.PostMessage(hWnd, win32con.WM_COMMAND, menuItemID, 0) - -##def findMenuItems(hWnd,wantedText): -## """Finds menu items whose captions contain the text""" -## hMenu = getTopMenu(hWnd) -## hMenuItemCount=win32gui.GetMenuItemCount(hMenu) -## -## for topItem in xrange(hMenuItemCount): -## - -def getMenuInfo(hWnd, menuItemPath): - '''TODO''' - - # Top level menu - hMenu = getTopMenu(hWnd) - - # Get top level menu's item count. Is there a better way to do this? - # Yes .. Tim Couper 22 Jul 2004 - hMenuItemCount=win32gui.GetMenuItemCount(hMenu) - -## for hMenuItemCount in xrange(256): -## try: -## _getMenuInfo(hMenu, hMenuItemCount) -## except WinGuiAutoError: -## break -## hMenuItemCount -= 1 - submenuInfo=None - - # Walk down submenus - for submenu in menuItemPath: - try: # submenu is an index - 0 + submenu - submenuInfo = _getMenuInfo(hMenu, submenu) - hMenu, hMenuItemCount = submenuInfo.submenu, submenuInfo.itemCount - except TypeError: # Hopefully, submenu is a menu name - try: - submenuIndex, new_hMenu, hMenuItemCount = _findNamedSubmenu(hMenu, - hMenuItemCount, - submenu) - submenuInfo = _getMenuInfo(hMenu, submenuIndex) - hMenu = new_hMenu - except WinGuiAutoError: - raise WinGuiAutoError("Menu path " + - repr(menuItemPath) + - " cannot be found.") - if submenuInfo==None: - raise WinGuiAutoError("Menu path " + - repr(menuItemPath) + - " cannot be found. (Null menu path?)") - - - return submenuInfo - -def _getMenuInfo(hMenu, uIDItem): - '''Get various info about a menu item. - - Arguments: - hMenu The menu in which the item is to be found. - uIDItem The item's index - - Returns: Menu item information object. This object is basically - a 'bunch' - (see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308). - It will have useful attributes: name, itemCount, - submenu, isChecked, isDisabled, isGreyed, and - isSeparator - - Raises: - WinGuiAutoError When the requested menu option isn't found. - - Usage example: submenuInfo = _getMenuInfo(hMenu, submenu) - hMenu, hMenuItemCount = submenuInfo.submenu, submenuInfo.itemCount - ''' - # An object to hold the menu info - class MenuInfo(Bunch): - pass - menuInfo = MenuInfo() - - # Menu state - menuState = ctypes.windll.user32.GetMenuState(hMenu, - uIDItem, - win32con.MF_BYPOSITION) - if menuState == -1: - raise WinGuiAutoError("No such menu item, hMenu=" + - str(hMenu) + - " uIDItem=" + - str(uIDItem)) - - # Menu name - menuName = ctypes.c_buffer("\000" * 32) - ctypes.windll.user32.GetMenuStringA(ctypes.c_int(hMenu), - ctypes.c_int(uIDItem), - menuName, ctypes.c_int(len(menuName)), - win32con.MF_BYPOSITION) - menuInfo.name = menuName.value - - # Sub menu info - menuInfo.itemCount = menuState >> 8 - if bool(menuState & win32con.MF_POPUP): - menuInfo.submenu = ctypes.windll.user32.GetSubMenu(hMenu, uIDItem) - else: - menuInfo.submenu = None - - menuInfo.isChecked = bool(menuState & win32con.MF_CHECKED) - menuInfo.isDisabled = bool(menuState & win32con.MF_DISABLED) - menuInfo.isGreyed = bool(menuState & win32con.MF_GRAYED) - menuInfo.isSeparator = bool(menuState & win32con.MF_SEPARATOR) - # ... there are more, but these are the ones I'm interested in thus far. - - return menuInfo - -def clickButton(hwnd): - '''Simulates a single mouse click on a button - - Arguments: - hwnd Window handle of the required button. - - Usage example: okButton = findControl(fontDialog, - wantedClass="Button", - wantedText="OK") - clickButton(okButton) - ''' - _sendNotifyMessage(hwnd, win32con.BN_CLICKED) - -def clickStatic(hwnd): - '''Simulates a single mouse click on a static - - Arguments: - hwnd Window handle of the required static. - - Usage example: TODO - ''' - _sendNotifyMessage(hwnd, win32con.STN_CLICKED) - -def doubleClickStatic(hwnd): - '''Simulates a double mouse click on a static - - Arguments: - hwnd Window handle of the required static. - - Usage example: TODO - ''' - _sendNotifyMessage(hwnd, win32con.STN_DBLCLK) - -def getComboboxItems(hwnd): - '''Returns the items in a combo box control. - - Arguments: - hwnd Window handle for the combo box. - - Returns: Combo box items. - - Usage example: fontCombo = findControl(fontDialog, wantedClass="ComboBox") - fontComboItems = getComboboxItems(fontCombo) - ''' - - return _getMultipleWindowValues(hwnd, - getCountMessage=win32con.CB_GETCOUNT, - getValueMessage=win32con.CB_GETLBTEXT) - -def selectComboboxItem(hwnd, item): - '''Selects a specified item in a Combo box control. - - Arguments: - hwnd Window handle of the required combo box. - item The reqired item. Either an index, of the text of the - required item. - - Usage example: fontComboItems = getComboboxItems(fontCombo) - selectComboboxItem(fontCombo, - random.choice(fontComboItems)) - ''' - try: # item is an index Use this to select - 0 + item - win32gui.SendMessage(hwnd, win32con.CB_SETCURSEL, item, 0) - _sendNotifyMessage(hwnd, win32con.CBN_SELCHANGE) - except TypeError: # Item is a string - find the index, and use that - items = getComboboxItems(hwnd) - itemIndex = items.index(item) - selectComboboxItem(hwnd, itemIndex) - -def getListboxItems(hwnd): - '''Returns the items in a list box control. - - Arguments: - hwnd Window handle for the list box. - - Returns: List box items. - - Usage example: docType = findControl(newDialog, wantedClass="ListBox") - typeListBox = getListboxItems(docType) - ''' - - return _getMultipleWindowValues(hwnd, - getCountMessage=win32con.LB_GETCOUNT, - getValueMessage=win32con.LB_GETTEXT) - -def selectListboxItem(hwnd, item): - '''Selects a specified item in a list box control. - - Arguments: - hwnd Window handle of the required list box. - item The reqired item. Either an index, of the text of the - required item. - - Usage example: docType = findControl(newDialog, wantedClass="ListBox") - typeListBox = getListboxItems(docType) - - # Select a type at random - selectListboxItem(docType, - random.randint(0, len(typeListBox)-1)) - ''' - try: # item is an index Use this to select - 0 + item - win32gui.SendMessage(hwnd, win32con.LB_SETCURSEL, item, 0) - _sendNotifyMessage(hwnd, win32con.LBN_SELCHANGE) - except TypeError: # Item is a string - find the index, and use that - items = getListboxItems(hwnd) - itemIndex = items.index(item) - selectListboxItem(hwnd, itemIndex) - -def getEditText(hwnd): - '''Returns the text in an edit control. - - Arguments: - hwnd Window handle for the edit control. - - Returns Edit control text lines. - - Usage example: pprint.pprint(getEditText(editArea)) - ''' - return _getMultipleWindowValues(hwnd, - getCountMessage=win32con.EM_GETLINECOUNT, - getValueMessage=win32con.EM_GETLINE) - -def setEditText(hwnd, text, append=False): - '''Set an edit control's text. - - Arguments: - hwnd The edit control's hwnd. - text The text to send to the control. This can be a single - string, or a sequence of strings. If the latter, each will - be become a a separate line in the control. - append Should the new text be appended to the existing text? - Defaults to False, meaning that any existing text will be - replaced. If True, the new text will be appended to the end - of the existing text. - Note that the first line of the new text will be directly - appended to the end of the last line of the existing text. - If appending lines of text, you may wish to pass in an - empty string as the 1st element of the 'text' argument. - - Usage example: print "Enter various bits of text." - setEditText(editArea, "Hello, again!") - setEditText(editArea, "You still there?") - setEditText(editArea, ["Here come", "two lines!"]) - - print "Add some..." - setEditText(editArea, ["", "And a 3rd one!"], append=True) - ''' - - # Ensure that text is a list - try: - text + '' - text = [text] - except TypeError: - pass - - # Set the current selection range, depending on append flag - if append: - win32gui.SendMessage(hwnd, - win32con.EM_SETSEL, - -1, - 0) - else: - win32gui.SendMessage(hwnd, - win32con.EM_SETSEL, - 0, - -1) - - # Send the text - win32gui.SendMessage(hwnd, - win32con.EM_REPLACESEL, - True, - os.linesep.join(text)) - -def setCheckBox(hwnd, state = True): - """ - Activates a CheckBox button. - - Inputs - - hwnd - Handle of GUI element - state - Boolean - True - Activate the Checkbox - False - Clear the CheckBox - - Outputs - - Integer -- Result of the Win32gui.SendMessage Command - - Note: There is a 3rd state to a CheckBox. Since it is not - common has been split to another function - setCheckBox_Indeterminate. - """ - win32gui.SendMessage( hwnd, - win32con.BM_SETCHECK, - win32con.BST_CHECKED, - 0 ) - - -def setCheckBox_Indeterminate(hwnd): - """ - Activates a CheckBox button. - - Inputs - - hwnd - Handle of GUI element - - Outputs - - Integer -- Result of the Win32gui.SendMessage Command - - """ - win32gui.SendMessage( hwnd, - win32con.BM_SETCHECK, - win32con.BST_INDETERMINATE, - 0 ) - -def getCheckBox(hwnd): - """ - Returns the status from a CheckBox button. - - Inputs - - hwnd - Handle of GUI element - - Outputs - - 0 - win32Gui send message error - win32con.BST_CHECKED- The Checkbox is checked - win32con.BST_INDETERMINATE - The Checkbox is checked and = - greyed out. - win32con.BST_UNCHECKED- The checkbox is not checked - =20 - """ - value = win32gui.SendMessage( hwnd, - win32con.BM_GETCHECK, - 0, - 0 ) - return value - -def _getMultipleWindowValues(hwnd, getCountMessage, getValueMessage): - ''' - - A common pattern in the Win32 API is that in order to retrieve a - series of values, you use one message to get a count of available - items, and another to retrieve them. This internal utility function - performs the common processing for this pattern. - - Arguments: - hwnd Window handle for the window for which items should be - retrieved. - getCountMessage Item count message. - getValueMessage Value retrieval message. - - Returns: Retrieved items. - ''' - result = [] - - MAX_VALUE_LENGTH = 256 - bufferlength = struct.pack('i', MAX_VALUE_LENGTH) # This is a C style int. - - valuecount = win32gui.SendMessage(hwnd, getCountMessage, 0, 0) - for itemIndex in range(valuecount): - valuebuffer = array.array('c', - bufferlength + - ' ' * (MAX_VALUE_LENGTH - len(bufferlength))) - valueLength = win32gui.SendMessage(hwnd, - getValueMessage, - itemIndex, - valuebuffer) - result.append(valuebuffer.tostring()[:valueLength]) - return result - -def _windowEnumerationHandler(hwnd, resultList): - '''win32gui.EnumWindows() callback. - - Pass to win32gui.EnumWindows() or win32gui.EnumChildWindows() to - generate a list of window handle, window text, window class tuples. - ''' - resultList.append((hwnd, - win32gui.GetWindowText(hwnd), - win32gui.GetClassName(hwnd))) - -def _buildWinLong(high, low): - '''Build a windows long parameter from high and low words. - - See http://support.microsoft.com/support/kb/articles/q189/1/70.asp - ''' - # return ((high << 16) | low) - return int(struct.unpack('>L', - struct.pack('>2H', - high, - low)) [0]) - -def _sendNotifyMessage(hwnd, notifyMessage): - '''Send a notify message to a control.''' - win32gui.SendMessage(win32gui.GetParent(hwnd), - win32con.WM_COMMAND, - _buildWinLong(notifyMessage, - win32api.GetWindowLong(hwnd, - win32con.GWL_ID)), - hwnd) - -def _normaliseText(controlText): - '''Remove '&' characters, and lower case. - - Useful for matching control text.''' - return controlText.lower().replace('&', '') - -def _findNamedSubmenu(hMenu, hMenuItemCount, submenuName): - '''Find the index number of a menu's submenu with a specific name.''' - for submenuIndex in range(hMenuItemCount): - submenuInfo = _getMenuInfo(hMenu, submenuIndex) - if _normaliseText(submenuInfo.name).startswith(_normaliseText(submenuName)): - return submenuIndex, submenuInfo.submenu, submenuInfo.itemCount - raise WinGuiAutoError("No submenu found for hMenu=" + - repr(hMenu) + - ", hMenuItemCount=" + - repr(hMenuItemCount) + - ", submenuName=" + - repr(submenuName)) - -def _dedup(thelist): - '''De-duplicate deeply nested list.''' - found=[] - def dodedup(thelist): - for index, thing in enumerate(thelist): - if isinstance(thing, list): - dodedup(thing) - else: - if thing in found: - del thelist[index] - else: - found.append(thing) - - -class Bunch(object): - '''See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308''' - - def __init__(self, **kwds): - self.__dict__.update(kwds) - - def __str__(self): - state = ["%s=%r" % (attribute, value) - for (attribute, value) - in self.__dict__.items()] - return '\n'.join(state) - -class WinGuiAutoError(Exception): - pass - -def self_test(): - '''Self test - drives Notepad and WordPad.''' - # I'd like to use unittest here, but I've no idea how to automate these - # tests. As it is, you just have to *watch* the test! ;-) - import pprint, random - - # NT/2K/XP versions of Notepad have a different menu stuctures. - WIN_VERSION_DECODE = {(1, 4, 0): "95", - (1, 4, 10): "98", - (1, 4, 90): "ME", - (2, 4, 0): "NT", - (2, 5, 0): "2K", - (2, 5, 1): "XP"} - win_version = os.sys.getwindowsversion() - win_version = WIN_VERSION_DECODE[win_version[3], win_version[0], win_version[1]] - print "win_version=", win_version - x=raw_input('->') - print "Let's see what top windows we have at the 'mo:" - pprint.pprint(dumpTopWindows()) - x=raw_input('->') - print "Open and locate Notepad" - os.startfile('notepad') - notepadWindow = findTopWindow(wantedClass='Notepad') - x=raw_input('->') - print "Look at some menu item info" - - print 'File menu' - print getMenuInfo(notepadWindow, ('file',)) - print "file|exit" - print getMenuInfo(notepadWindow, ('file', 'exit')) - x=raw_input('->') - print "Open and locate the 'replace' dialogue" - if win_version in ["NT"]: - activateMenuItem(notepadWindow, ['search', 'replace']) - elif win_version in ["2K", "XP"]: - activateMenuItem(notepadWindow, ['edit', 'replace']) - else: - raise Exception("Tests not written for this OS yet. Feel free!") - replaceDialog = findTopWindow(wantedText='Replace', wantedClass="#32770") - x=raw_input('->') - print "Locate the 'find' edit box" - findValue = findControls(replaceDialog, wantedClass="Edit")[0] - x=raw_input('->') - print "Enter some text - and wait long enough for it to be seen" - setEditText(findValue, "Hello, mate!") - time.sleep(.5) - x=raw_input('->') - - print "Locate the 'cancel' button, and click it." - cancelButton = findControl(replaceDialog, - wantedClass="Button", - wantedText="Cancel") - clickButton(cancelButton) - x=raw_input('->') - print "Open and locate the 'font' dialogue" - if win_version in ["NT"]: - activateMenuItem(notepadWindow, ['edit', 'set font']) - elif win_version in ["2K", "XP"]: - activateMenuItem(notepadWindow, ['format', 'font']) - print findTopWindows(wantedText='Font', wantedClass="#32770") - x=raw_input('->') - fontDialog = findTopWindow(wantedText='Font', wantedClass="#32770") - - print "Let's see if dumping works. Dump the 'font' dialogue contents:" - pprint.pprint(dumpWindow(fontDialog)) - x=raw_input('->') - - print "Change the font" - fontCombos = findControls(fontDialog, wantedClass="ComboBox") - print "Find the font selection combo" - for fontCombo in fontCombos: - fontComboItems = getComboboxItems(fontCombo) - if 'Arial' in fontComboItems: - break - x=raw_input('->') - print "Select at random" - selectComboboxItem(fontCombo, random.choice(fontComboItems)) - time.sleep(.5) - - okButton = findControl(fontDialog, wantedClass="Button", wantedText="OK") - clickButton(okButton) - x=raw_input('->') - - print "Locate notepad's edit area, and enter various bits of text." - editArea = findControl(notepadWindow,wantedClass="Edit") - setEditText(editArea, "Hello, again!") - time.sleep(.5) - setEditText(editArea, "You still there?") - time.sleep(.5) - setEditText(editArea, ["Here come", "two lines!"]) - time.sleep(.5) - x=raw_input('->') - - print "Add some..." - setEditText(editArea, ["", "And a 3rd one!"], append=True) - time.sleep(.5) - - print "See what's there now:" - pprint.pprint(getEditText(editArea)) - x=raw_input('->') - - print "Exit notepad" - activateMenuItem(notepadWindow, ('file', 'exit')) - time.sleep(.5) - - print "Don't save." - saveDialog = findTopWindow(selectionFunction=lambda hwnd: - win32gui.GetWindowText(hwnd)=='Notepad') - noButton = findControl(saveDialog,wantedClass="Button", wantedText="no") - clickButton(noButton) - x=raw_input('->') - - print "Check you get an exception for non-existent top window" - try: - findTopWindow(wantedText="Banana") - raise Exception("Test failed") - except WinGuiAutoError, winGuiAutoError: - print "Yup, got: ", str(winGuiAutoError) - x=raw_input('->') - print "OK, now we'll have a go with WordPad." - os.startfile('wordpad') - wordpadWindow = findTopWindow(wantedText='WordPad') - x=raw_input('->') - print "Open and locate the 'new document' dialog." - activateMenuItem(wordpadWindow, [0, 0]) - newDialog = findTopWindow(wantedText='New', wantedClass="#32770") - x=raw_input('->') - print "Check you get an exception for non-existent control" - try: - findControl(newDialog, wantedClass="Banana") - raise Exception("Test failed") - except WinGuiAutoError, winGuiAutoError: - print "Yup, got: ", str(winGuiAutoError) - x=raw_input('->') - print "Locate the 'document type' list box" - docType = findControl(newDialog, wantedClass="ListBox") - typeListBox = getListboxItems(docType) - print "getListboxItems(docType)=", typeListBox - x=raw_input('->') - print "Select a type at random" - selectListboxItem(docType, random.randint(0, len(typeListBox)-1)) - time.sleep(.5) - clickButton(findControl(newDialog, wantedClass="Button", wantedText="OK")) - x=raw_input('->') - print "Check you get an exception for non-existent menu path" - try: - activateMenuItem(wordpadWindow, ('not', 'there')) - raise Exception("Test failed") - except WinGuiAutoError, winGuiAutoError: - print "Yup, got: ", str(winGuiAutoError) - x=raw_input('->') - print "Check you get an exception for non-existent menu item" - try: - activateMenuItem(wordpadWindow, ('file', 'missing')) - raise Exception("Test failed") - except WinGuiAutoError, winGuiAutoError: - print "Yup, got: ", str(winGuiAutoError) - x=raw_input('->') - print "Exit wordpad" - activateMenuItem(wordpadWindow, ('file', 'exit')) - x=raw_input('->') - print "Err, that's it." - -if __name__ == '__main__': - self_test() \ No newline at end of file diff --git a/agent/interfaces.py b/agent/interfaces.py deleted file mode 100644 index ff2db0d..0000000 --- a/agent/interfaces.py +++ /dev/null @@ -1,264 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -loops client agent interfaces. - -$Id$ -""" - -from zope.interface import Interface, Attribute - - -class IAgent(Interface): - """ An agent watches its client, looks for resources to process, - and transfers these to its server. - """ - - scheduler = Attribute('IScheduler instance.') - transporter = Attribute('The transporter to be used for transferring ' - 'objects.') - - -class IScheduler(Interface): - """ Manages jobs and cares that they are started at the appropriate - time. - """ - - logger = Attribute('Logger instance to be used for recording ' - 'job execution and execution results.') - - def schedule(job, startTime=None): - """ Register the job given for execution at the intended start - date/time (an integer timestamp) and return the job. - - If the start time is not given schedulethe job for immediate - start. Return the start time with which the job has been - scheduled - this may be different from the start time - supplied. - """ - - def getJobsToExecute(startTime=None): - """ Return a collection of jobs that are scheduled for execution at - or before the date/time given. - - If startTime is None the current date/time is used. - """ - - -class IScheduledJob(Interface): - """ A job that will be executed on some external triggering at - a predefined date and time. - """ - - startTime = Attribute('Date/time at which the job should be executed.') - params = Attribute('Mapping with key/value pairs to be used by ' - 'the ``execute()`` method.') - repeat = Attribute('Number of seconds after which the job should be ' - 'rescheduled. Do not repeat if 0.') - successors = Attribute('Jobs to execute immediately after this ' - 'one has been finished.') - whenStarted = Attribute('A callable with one argument (the job) that will ' - 'be called when the job has started.') - whenfinished = Attribute('A callable with two arguments, the job and the ' - 'result of running the job, that will be called when ' - 'the job has finished.') - - def execute(): - """ Execute the job. - - Store log information about job execution in a log record. - """ - - def reschedule(startTime): - """ Re-schedule the job, setting the date/time the job should be - executed again. - """ - - -class ILogger(Interface): - """ Ordered collection (list) of log records. - """ - - externalLoggers = Attribute('A collection of logger objects ' - 'to which the logging records should be written.') - - def setup(): - """ Initialize the logger with the current configuration settings. - """ - - def log(data): - """ Record the information given by the ``data`` argument - (a mapping). - """ - - -class ILogRecord(Interface): - """ - """ - - def __str__(): - """ Return a string representation suitable for writing on a - log file. - """ - - -class ICrawlingJob(IScheduledJob): - """ Collects resources. - """ - - predefinedMetadata = Attribute('A mapping with metadata to be used ' - 'for all resources found.') - - def collect(): - """ Return a deferred that upon callback will provide a - collection of resource objects that should be transferred - to the server. - - Use the selection criteria given to filter the resources that - should be collected. - """ - - -class IResource(Interface): - """ Represents a data object that is collected by a crawler and - will be transferred to the server. - """ - - data = Attribute("A string, file, or similar representation of the " - "resource's content") - path = Attribute('A filesystem path or some other information ' - 'uniquely identifying the resource on the client ' - 'machine for the current user.') - application = Attribute('The name of the application that provided ' - 'the resource, e.g. "filesystem" or "mail".') - metadata = Attribute('Information describing this resource; ' - 'should be an IMetadataSet object.') - - -class IMetadataSet(Interface): - """ Metadata associated with a resource; a mapping. - """ - - def asXML(): - """ Return an XML string representing the metadata set. - - If this metadata set contains other metadata sets - (nested metadata) this will be converted to XML as well. - """ - - def set(key, value): - """ Set a metadata element. - - The value may be a string or another metadata set - (nested metadata). - """ - - -class ITransporter(Interface): - """ Transfers collected resources to the server. - """ - - serverURL = Attribute('URL of the server the resources will be ' - 'transferred to. The URL also determines the ' - 'transfer protocol, e.g. HTTP or FTP.') - method = Attribute('Transport method, e.g. PUT.') - machineName = Attribute('Name under which the local machine is ' - 'known to the server.') - userName = Attribute('User name for logging in to the server.') - password = Attribute('Password for logging in to the server.') - - def createJob(): - """ Return a transport job for this transporter. - """ - - def transfer(resource): - """ Transfer the resource (an object providing IResource) - to the server and return a Deferred. - """ - - -class ITransportJob(IScheduledJob): - """ A job managing the the transfer of a resource to the server. - """ - - transporter = Attribute('The transporter object to use for transer.') - - -class IConfigurator(Interface): - """ Manages (stores and receives) configuration information. - """ - - filename = Attribute('The path to a file with configuration parameters.') - - def load(p=None, filename=None): - """ Load configuration directly from a string or an open file - (if ``p`` is set) or using the ``filename`` parameter (a path). - - If no string and no filename is given the configuration - file is searched in the user's home folder. - - If the configuration is loaded from a file using the - ``filename`` parameter or from the default location the - path is stored in the ``filename`` attribute. - """ - - def save(filename=None): - """ Save configuration settings to the file given, or to the - file from which it was loaded, or to the default location. - """ - - -# future extensions - -class IPackageManager(Interface): - """ Allows to install, update, or remove software packages (plugins, - typically as Python eggs) from a server. - """ - - sources = Attribute('A list of URLs that provide software packages. ') - - def getInstalledPackages(): - """ Return a list of dictionaries, format: - [{'name': name, 'version': version, - 'date': date_time_of_installation,}, ...] - """ - - def getUpdateCandidates(): - """ Return a list of dictionaries with information about updateable - packages. - """ - - def installPackage(name, version=None, source=None): - """ Install a package. - If version is not given try to get the most recent one. - If source is not given search the sources attribute for the - first fit. - """ - - def updatePackage(name, version=None, source=None): - """ Update a package. - If version is not given try to get the most recent one. - If source is not given search the sources attribute for the - first fit. - """ - - def removePackage(name): - """ Remove a package from this agent. - """ - diff --git a/agent/log.py b/agent/log.py deleted file mode 100644 index 6732edf..0000000 --- a/agent/log.py +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Log information management. - -$Id$ -""" - -import logging -import sys -import time -from zope.interface import implements - -from loops.agent.interfaces import ILogger, ILogRecord - - -class LogRecord(object): - - implements(ILogRecord) - - datefmt = '%Y-%m-%dT%H:%S' - - def __init__(self, logger, data): - self.logger = logger - self.data = data - self.timeStamp = time.time() - - def __str__(self): - msg = [str(time.strftime(self.datefmt, time.localtime(self.timeStamp)))] - for k in sorted(self.data): - msg.append('%s:%s' % (str(k), str(self.data[k]))) - return ' '.join(msg) - - -class Logger(list): - - implements(ILogger) - - recordFactory = LogRecord - - - def __init__(self, agent): - self.agent = agent - self.setup() - - def setup(self): - self.externalLoggers = [] - conf = self.agent.config.logging - if conf.standard: - logger = logging.getLogger() - logger.level = conf.standard - logger.addHandler(logging.StreamHandler(sys.stdout)) - self.externalLoggers.append(logger) - - def log(self, data): - record = self.recordFactory(self, data) - self.append(record) - for logger in self.externalLoggers: - logger.info(str(record)) - diff --git a/agent/loops.tac b/agent/loops.tac deleted file mode 100644 index 96a86fd..0000000 --- a/agent/loops.tac +++ /dev/null @@ -1,22 +0,0 @@ -""" -Start with ``twistd -noy loops/agent/loops.tac``. - -$Id$ -""" - -from twisted.application import internet, service -from nevow import appserver - -from loops.agent.core import Agent -from loops.agent.ui.web import AgentHome - -agent = Agent() -conf = agent.config -port = conf.ui.web.port or 10095 - -application = service.Application('LoopsAgent') - -site = appserver.NevowSite(resource=AgentHome(agent)) -webServer = internet.TCPServer(port, site) -webServer.setServiceParent(application) - diff --git a/agent/schedule.py b/agent/schedule.py deleted file mode 100644 index 49794a1..0000000 --- a/agent/schedule.py +++ /dev/null @@ -1,118 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Job scheduling. - -$Id$ -""" - -from time import time -from twisted.internet import reactor -from twisted.internet.defer import Deferred, succeed -from zope.interface import implements - -from loops.agent.interfaces import IScheduler, IScheduledJob - - -class Scheduler(object): - - implements(IScheduler) - - def __init__(self, agent): - self.agent = agent - self.queue = {} - - @property - def logger(self): - return self.agent.logger() - - def schedule(self, job, startTime=None): - if startTime is None: - startTime = int(time()) - job.startTime = startTime - job.scheduler = self - while startTime in self.queue: - startTime += 1 - self.queue[startTime] = job - reactor.callLater(startTime-int(time()), job.run) - return startTime - - def getJobsToExecute(startTime=0): - return [j for j in self.queue.values() if startTime <= j.startTime] - - -class Job(object): - - implements(IScheduledJob) - - scheduler = None - - whenStarted = lambda self, job: None - whenFinished = lambda self, job, result: None - - def __init__(self, **params): - self.startTime = 0 - self.params = params - self.successors = [] - self.repeat = 0 - - def execute(self): - """ Must be overridden by subclass. - """ - return succeed('OK') - - def reschedule(self, startTime): - return self.scheduler.schedule(self.copy(), startTime) - - def run(self): - d = self.execute() - d.addCallback(self.finishRun) - d.addErrback(self.logError) - self.whenStarted(self) - # TODO: logging - - def finishRun(self, result): - # remove from queue - del self.scheduler.queue[self.startTime] - # run successors - for job in self.successors: - job.params['result'] = result - #job.run() - self.scheduler.schedule(job) - self.whenFinished(self, result) - # TODO: logging - # reschedule if necessary - if self.repeat: - self.reschedule(int(time() + self.repeat)) - - def logError(self, error): - print '*** error on running job:', error - - def copy(self): - newJob = Job() - newJob.params = self.params - newJob.successors = [s.copy() for s in self.successors] - - -class Stopper(Job): - - def execute(self): - reactor.stop() - return succeed('Agent stopped.') - diff --git a/agent/testing/__init__.py b/agent/testing/__init__.py deleted file mode 100644 index 4bc90fb..0000000 --- a/agent/testing/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -$Id$ -""" - diff --git a/agent/testing/client.py b/agent/testing/client.py deleted file mode 100644 index 40ecff8..0000000 --- a/agent/testing/client.py +++ /dev/null @@ -1,53 +0,0 @@ -#! /usr/bin/env python -# -# Copyright (c) 2007 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 -# - -""" -A dummy client application for testing purposes. - -Run this from above the loops directory with:: - - python loops/agent/testing/client.py - -$Id$ -""" - -import os -import sys -from time import time -from twisted.internet import reactor - -from loops.agent.core import Agent -from loops.agent.crawl.filesystem import CrawlingJob -from loops.agent.transport.base import Transporter -from loops.agent.tests import baseDir - -if len(sys.argv) > 1: - cfgName = sys.argv[1] -else: - cfgName = os.path.join(baseDir, 'testing', 'testing.cfg') -cfg = open(cfgName) - -agent = Agent(cfg.read()) -agent.scheduleJobsFromConfig(stop=True) - -print 'Starting reactor.' - -reactor.run() - -print 'Reactor stopped.' diff --git a/agent/testing/crawl.py b/agent/testing/crawl.py deleted file mode 100644 index 66952d5..0000000 --- a/agent/testing/crawl.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -A dummy crawler for testing purposes. - -$Id$ -""" - -from twisted.internet import reactor -from twisted.internet.defer import Deferred -from zope.interface import implements - -from loops.agent.interfaces import IResource -from loops.agent.crawl.base import CrawlingJob as BaseCrawlingJob - - -class CrawlingJob(BaseCrawlingJob): - - def collect(self): - deferred = self.deferred = Deferred() - # replace this with the real stuff: - reactor.callLater(0, self.dataAvailable) - return deferred - - def dataAvailable(self): - self.deferred.callback([DummyResource()]) - - -class DummyResource(object): - - implements(IResource) - - data = 'Dummy resource data for testing purposes.' - path = '/dummy/data' - application = 'dummy' - metadata = None diff --git a/agent/testing/data/file1.txt b/agent/testing/data/file1.txt deleted file mode 100644 index 02c267f..0000000 --- a/agent/testing/data/file1.txt +++ /dev/null @@ -1 +0,0 @@ -Data from file1.txt \ No newline at end of file diff --git a/agent/testing/data/subdir/file2.txt b/agent/testing/data/subdir/file2.txt deleted file mode 100644 index 493d31b..0000000 --- a/agent/testing/data/subdir/file2.txt +++ /dev/null @@ -1 +0,0 @@ -Data from file2.txt \ No newline at end of file diff --git a/agent/testing/server.py b/agent/testing/server.py deleted file mode 100644 index 5c48333..0000000 --- a/agent/testing/server.py +++ /dev/null @@ -1,63 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -A dummy webserver for testing the loops.agent HTTP transport. - -Run this with:: - - twistd -noy loops/agent/testing/server.py - -$Id$ -""" - -from twisted.application import service, internet -from twisted.web import http - - -class RequestHandler(http.Request): - - def process(self): - print '***', repr(self.content.read()) - if self.method in ('GET', 'POST'): - self.write('

Hello World

') - self.write('

dir(self): %s

' % dir(self)) - self.write('

self.path: %s

' % self.path) - self.write('

self.uri: %s

' % self.uri) - self.write('

self.args: %s

' % self.args) - self.finish() - - -class HttpServer(http.HTTPChannel): - - requestFactory = RequestHandler - - -class HttpFactory(http.HTTPFactory): - - protocol = HttpServer - - -class HttpService(internet.TCPServer): - - def __init__(self): - internet.TCPServer.__init__(self, 8123, HttpFactory()) - - -application = service.Application('Simple Webserver') -HttpService().setServiceParent(application) diff --git a/agent/testing/testing.cfg b/agent/testing/testing.cfg deleted file mode 100644 index 133d2f6..0000000 --- a/agent/testing/testing.cfg +++ /dev/null @@ -1,6 +0,0 @@ -crawl[0].type = 'filesystem' -crawl[0].directory = 'loops/agent/testing/data' -crawl[0].pattern = '*.txt' -crawl[0].transport = 'httpput' -crawl[0].repeat = 0 -transport.serverURL = 'http://localhost:8123/loops' diff --git a/agent/testing/transport.py b/agent/testing/transport.py deleted file mode 100644 index 3cd7d35..0000000 --- a/agent/testing/transport.py +++ /dev/null @@ -1,47 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -A dummy transport for testing purposes. - -$Id$ -""" - -from twisted.internet import reactor -from twisted.internet.defer import succeed -from zope.interface import implements - -from loops.agent.interfaces import ITransportJob, ITransporter -from loops.agent.transport.base import Transporter as BaseTransporter - - -class Transporter(BaseTransporter): - - def transfer(self, resource): - data = resource.data - if type(data) is file: - text = data.read() - data.close() - else: - text = data - metadata = resource.metadata - if metadata is not None: - print 'Metadata:', metadata - print 'Transferring:', text - return succeed('OK') - diff --git a/agent/tests.py b/agent/tests.py deleted file mode 100755 index 0b81ce0..0000000 --- a/agent/tests.py +++ /dev/null @@ -1,75 +0,0 @@ -#! /usr/bin/env python -# -# Run with ``trial2.4 tests.py`` to execute the twisted unit tests. -# Run with ``python tests.py`` to execute the doctests. -# - -# $Id$ - -import unittest as standard_unittest -import doctest -import os, time -from twisted.internet import reactor -from twisted.internet.defer import Deferred -from twisted.trial import unittest - -from loops.agent.schedule import Job -try: - from loops.agent.core import Agent # needs twisted.internet.task.coiterate - ignore = False -except ImportError: # wrong environment, skip all loops.agent tests - print 'Skipping loops.agent' - ignore = True - -baseDir = os.path.dirname(__file__) - - -class Tester(object): - - def iterate(self, n=10, delays={}): - for i in range(n): - delay = delays.get(i, 0) - reactor.iterate(delay) - -tester = Tester() - - -class TestJob(Job): - - def execute(self, deferred, **kw): - d = super(TestJob, self).execute(**kw) - #print 'executing' - deferred.callback('Done') - return d - - -class Test(unittest.TestCase): - "Basic tests for the loops.agent package." - - def setUp(self): - self.agent = Agent() - - def tearDown(self): - pass - - def testScheduling(self): - d = Deferred() - job = TestJob() - job.params['deferred'] = d - w = self.agent.scheduler.schedule(job, int(time.time())+1) - return d - - -def test_suite(): - if ignore: - return standard_unittest.TestSuite() # do nothing - flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS - return standard_unittest.TestSuite(( - #standard_unittest.makeSuite(Test), - doctest.DocFileSuite('README.txt', optionflags=flags), - doctest.DocFileSuite('crawl/filesystem.txt', optionflags=flags), - doctest.DocFileSuite('transport/httpput.txt', optionflags=flags), - )) - -if __name__ == '__main__': - standard_unittest.main(defaultTest='test_suite') diff --git a/agent/transport/__init__.py b/agent/transport/__init__.py deleted file mode 100644 index 4bc90fb..0000000 --- a/agent/transport/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -$Id$ -""" - diff --git a/agent/transport/base.py b/agent/transport/base.py deleted file mode 100644 index ceff3dc..0000000 --- a/agent/transport/base.py +++ /dev/null @@ -1,135 +0,0 @@ -# -# Copyright (c) 2007 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 -# - -""" -Transporter base classes. - -$Id$ -""" - -from base64 import b64encode -from twisted.internet import reactor -from twisted.internet.defer import Deferred, fail -from twisted.web.client import getPage -from zope.interface import implements - -from loops.agent.interfaces import ITransporter, ITransportJob -from loops.agent.schedule import Job - - -class TransportJob(Job): - - implements(ITransportJob) - - def __init__(self, transporter): - super(TransportJob, self).__init__() - self.transporter = transporter - self.resources = [] - self.deferred = None - - def execute(self): - result = self.params.get('result') - if result is None: - return fail('No data available.') - self.resources = result - self.deferred = Deferred() - d = self.transporter.transfer(self.resources.pop()) - d.addCallback(self.transferDone).addErrback(self.logError) - return self.deferred - - def transferDone(self, result): - self.logTransfer(result) - if self.resources: - d = self.transporter.transfer(self.resources.pop()) - d.addCallback(self.transferDone).addErrback(self.logError) - else: - self.deferred.callback('OK') - - def logTransfer(self, result, err=None): - #print 'transfer successful; remaining:', len(self.resources) - # TODO: logging - # self.transporter.agent.logger.log(...) - pass - - def logError(self, error): - print '*** error on transfer', self.transporter.serverURL, error - self.deferred.errback(error) - - -class Transporter(object): - - implements(ITransporter) - - jobFactory = TransportJob - - serverURL = 'http://localhost:8080' - method = 'PUT' - machineName = 'unknown' - userName = 'nobody' - password = 'secret' - - def __init__(self, agent, **params): - self.agent = agent - for k, v in params.items(): - setattr(self, k ,v) - self.deferred = None - - def createJob(self): - return self.jobFactory(self) - - def transfer(self, resource): - data = resource.data - if type(data) is file: - text = data.read() - data.close() - else: - text = data - path = resource.path - app = resource.application - metadata = resource.metadata - auth = b64encode(self.userName + ':' + self.password) - headers = {'Authorization': 'Basic ' + auth} - url = self.makePath('.data', app, path) - d = getPage(url, method=self.method, headers=headers, postdata=text) - if metadata is None: - d.addCallback(self.finished) - else: - d.addCallback(self.transferMeta, app, path, headers, metadata.asXML()) - self.deferred = Deferred() - return self.deferred - - def transferMeta(self, result, app, path, headers, text): - url = self.makePath('.meta', app, path) - d = getPage(url, method=self.method, headers=headers, postdata=text) - d.addCallback(self.finished) - - def finished(self, result): - self.deferred.callback(result) - - def makePath(self, infoType, app, path, extension=None): - if path.startswith('/'): - path = path[1:] - url = self.serverURL - if url.endswith('/'): - url = url[:-1] - fullPath = '/'.join((url, infoType, - self.machineName, self.userName, app, path)) - if extension: - fullPath += '.' + extension - return fullPath - diff --git a/agent/transport/httpput.txt b/agent/transport/httpput.txt deleted file mode 100644 index da808a4..0000000 --- a/agent/transport/httpput.txt +++ /dev/null @@ -1,15 +0,0 @@ -====================================================== -loops.agent.transport.httpput - The HTTP PUT Transport -====================================================== - - ($Id$) - - >>> from time import time - - >>> from loops.agent.core import Agent - >>> from loops.agent.transport.base import Transporter - - >>> agent = Agent() - >>> transporter = Transporter(agent) - >>> job = transporter.createJob() - diff --git a/agent/ui/StartUpAgentUI.tac.py b/agent/ui/StartUpAgentUI.tac.py deleted file mode 100644 index 82117ff..0000000 --- a/agent/ui/StartUpAgentUI.tac.py +++ /dev/null @@ -1,26 +0,0 @@ -#------------------------------------------------- -# StartUpAgentUI.tac.py -# start the twisted webserver and initialize the -# nevow framework/application -# author: Juergen Menzinger -# version: 01.alpha -# start with twistd.py -noy StartUpAgentUI.tac.py -# requires AgentStart.html -# AgentJobView.html -# AgentJobViewDetail.html -# AgentOutlookMailView.html -#------------------------------------------------- - - -from twisted.application import internet -from twisted.application import service -from nevow import appserver -import web -import sys, os, socket - - -application = service.Application('loops agent') -site = appserver.NevowSite(web.AgentHome()) -webServer = internet.TCPServer(8080, site) -webServer.setServiceParent(application) - diff --git a/agent/ui/__init__.py b/agent/ui/__init__.py deleted file mode 100644 index 4bc90fb..0000000 --- a/agent/ui/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -$Id$ -""" - diff --git a/agent/ui/joblist.txt b/agent/ui/joblist.txt deleted file mode 100644 index cab2483..0000000 --- a/agent/ui/joblist.txt +++ /dev/null @@ -1,4 +0,0 @@ -FileSysCont_1234;Running;daily;*.doc;C:\Documents;size<100kByte;transfer(http:loops.sampleserver.de) -MailCrawl_2121;Running;daily;Subject:Update;Outlook;Account=Account_Root;transfer(http:loops.sampleserver.de) -MailCrawl_4040;Paused;20s;Subject:Update;Outlook;Account=Account_Root;transfer(http:loops.sampleserver.de) -FileSysCont_8797;Stopped;10m;*.jpg;C:\Tmp;size<100kByte;transfer(http:loops.sampleserver.de) \ No newline at end of file diff --git a/agent/ui/resources/base.css b/agent/ui/resources/base.css deleted file mode 100644 index c5608fe..0000000 --- a/agent/ui/resources/base.css +++ /dev/null @@ -1,99 +0,0 @@ -/* - $Id$ - - based on http://www.tjkdesign.com/articles/liquid/4.asp - -*/ - -body { - min-width:640px; - margin: 0; - padding: 0; - font: 9pt Verdana, Tahoma, Arial, Helvetica, sans-serif; - background-color: white; - color: #000040; -} - -#global,#menu,#sub-section,#footer { - overflow: hidden; - display: inline-block; -} - -#content { - display: inline-block; -} - -#global,#footer {width:100%} -#menu,#content,#sub-section {float:left} -#menu {width:20%} -#content {width:62%} -#sub-section {width:17%} -#footer {clear:left} - -/* more general stuff */ - -.top image { - margin-top: -1px; -} - -div.box { - margin: 12px 12px 8px 10px; - border: 1px solid #ccc; - border-bottom: none; -} - -div.box h4 { - font: 110% Verdana, Tahoma, Arial, Helvetica, sans-serif; - color: #000040; - border: none; - border-bottom: 1px solid #ccc; - padding: 4px; - padding-top: 1px; - padding-bottom: 3px; - background-color: #ddd; - height: auto; -} - -table.listing { - margin: 1px; - margin-top: 6px; - -} - -table.listing th { - font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif; - color: #000040; -} - -.footer { - text-align: center; - border-top: 1px solid #ccc; - border-bottom: none; - margin-top: 12px; - padding-top: 6px; -} - -.itemViews { - border-bottom-width: 2px; -} - -.button { - margin: 1em 0 1em 0; -} - -.button a { - padding: 2px 4px 2px 4px; - background-color: #e8e8e8; - text-decoration: None; - color: Black; - border-width: 2px; - border-style: solid; - border-color: #f4f4f4 #989898 #989898 #f4f4f4; -} - -pre { - background-color: #f4f4f4; -} - -#footer { border-bottom: none; } - diff --git a/agent/ui/resources/custom.css b/agent/ui/resources/custom.css deleted file mode 100644 index db03dc5..0000000 --- a/agent/ui/resources/custom.css +++ /dev/null @@ -1,28 +0,0 @@ -/* - $Id$ - - cyberconcepts specialties - -*/ - -body { - color: #242424; -} - -a { - text-decoration: none; - color: #344080; - background-color: transparent; -} - -.top { - border-bottom: 1px solid #d0d0d0; - margin-bottom: 2px; - /*background-image: url('bg_cyberview.gif') - height: 75px;*/ -} - -table.listing tr:hover { - background-color: #FEFE82; -} - diff --git a/agent/ui/resources/favicon.png b/agent/ui/resources/favicon.png deleted file mode 100644 index 1fc74855de236f397eaa38e086a2a0e0d1006cf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx-l>??(%eT45_%4^hf__1CL5W1($#|!$wPi`>kE4nLb)7*zq6nboj%vj8UOs#srhs z%pV06_}G#X75LmIakC@|DwMH4TI`U+Ihlb&A%$6v@uRm&otOYOgQ2>>bH&d6&P9^U z4h)LYALKfuC;YhN@P_GYpV1@>kF(km>_7u00uJUW#C0F(Z%mT7!FeQlf++)wfO3>V zQ-hGeeU?X;9p*3{+22^9CUD#N@Jp^uNev84k^%fno;Lo_7N}=`G~eM5Q-?V7NA(p} zdJ27PNtOy}?QPuV0{4NAnZUSUg`q=ntFgd)XJKQeN1HQ}+8LNSIJ1vMgh z?!d5-p|juNk5tEg$Gr@nPU-7({dYXvD&AqaAnpf?fP;em2Z!Xx`Xby+AHx*paUCgM zI7_*4hfK%cg_*6AUH_R_1WvHVU&vuv>fo#}kNL>5fa#)6Paiexkc>2RU|6`EIUwiC z0@ZcjJ^mOeggIYh5O8?G_Qc^cBeTiB=gc3OGHx_5oY>8};6-~TIP6LmOfq2bnz+5; zhoOMJqtJPlBx6t*P2k' + title + ''; - var tr = document.createElement('tr'); - tr.appendChild(td); - node.appendChild(tr); -} - diff --git a/agent/ui/resources/loops_logo.png b/agent/ui/resources/loops_logo.png deleted file mode 100644 index 15123ab009484bfd25a73db3ad2db1c3a103b482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVqk00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmYE+YT{E+YYWr9XB6000McNliru)&w010}JeK&C~z@275_F zK~yNu?VEY17gZF;KjT&|xnyo*^~yGkh{BdfX<~%EY|%6dDz{ROnOG?pVw9%RVp>X? zhcbl17A-{$L(^Q^+)4y3%QTb5GSjkGZat?z=88Xu`K>cQe?H&+!Vl-&JNKTscfND( z*#rv<3kwSi3kwS;s!>%30vAMd9TJge?Y31v_%3yUGRI?UN}CH*Iv)Gu%v~oB~kSw(B;5b;1b`Z!twBYmwLc7ppEY`3RvoR_=U`0%Wm;$PoUbsRG z9OwvK2fPWiDO83f(dlllcvJzifx|#OV1Vz^5a5j+dVi?vI)!!Cs3k)#FSS9cuFant4 zc8W61X+@>@b{tPR`{ zes3c3S&TW$fx204+a)4fbDXjzFdR55rLDs8*lV=Y0pDd4FcBzAxo10+fJCa=80cb* zwfewqc?{pFs%wFLz*rI4kwH#Npr0XW8PFjQIqOw*2e2BLDI&Wg<{y#a0~lyLnSthI z-x(&6s?IgnJAjEI@=J!bv;iIk+MA}^fitqKVY{k+W3CSYBY{d0`J<%AWts)9Bu$)1 zL>h-Al%@bJ5W}s`Bu)a$>U>s0I5Z^lhtO6Doj}70Y9Jm8$3N$iWYr5mHrjau#A=wc1oN3PbLZ3g^_0%GW{>H@?V5Er54Qb*VfeV2tS@tbT3)MCxX<+Q5 zna0JR0o-C-`#!$QD~?xK)VkPlqM;(95yxP2qXkAMAY(6ZtB9fwWxzUuYK_sr z?ow%@Oy~=|71d!}(lM&)17l*y@PO`71Tu2gscJuCNZ%B5JyW9yWaR8q)#nl*(RsiN zs+#M^wHTNzBFi(n;2tY|mu>_J*q=<>LEmMl@!WQvs%ei|B9iUFj>J{}=?Ikaf~$-g zkBiw^`m2DKa+v1_g6MIkuuFp+e?5SCsyg4q&@vI}Psd|*Dp?$lO^(OUaR^jw64fY} z4`z1`_q}CeHc6ogLEy7{sn%{uR2s}8D8Wb)Pg)YyEUh)jghzfn9oAD!5{=8$X_A;% z#gx%eRV%W{ITvW1z|^E^**`$)|`qYDyDbVUsYQJGt%_m2izM| zMqS`lReh5nFBF9d98Yq_!H25)NCvy77db?tRdo5%jDnHqm^7a?7MKBUIY3(P*QjvGOGCbE;PPk|2F@+8`E* zOyqf5!q2o7cqb(13*eI?XrZ_B)Q*KC1Viv#vCZtu%OVnvP~QW5L{K;%7aM>+BC@zt zBuZD=*wq-uVH`=9KT_2)g4&rrz}0y^$0XppaP~TloN|IB<#>Y7f3El!7zJz$#WoEb Qxc~qF07*qoM6N<$f+`ayqW}N^ diff --git a/agent/ui/resources/loops_logo2.png b/agent/ui/resources/loops_logo2.png deleted file mode 100644 index 2c0af50316d591cc9b6224deb85f3e03f300e58d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13258 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 - - - - - - - - -
-
-
- - - - - -
-
-
- -
-
-

-
-
-

Startpage

- -
    -
  • This page contains some information about the loops agent UI. On the left side you can find the - navigation panel where you can choose between a general job overview to be displayed, add a job or change the - logging options. Also below this navigation menue you will find a button which switches the User mode between - Professional and Simple Mode. This button is to be found on this start page only.
  • -
-
-
-

Version

-
    -
  • Agent:
  • -
-
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/filecrawl.html b/agent/ui/templates/filecrawl.html deleted file mode 100644 index 8e6e82c..0000000 --- a/agent/ui/templates/filecrawl.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent: Create Filesystem Crawler Job

- -
    -
  • Configuration page for Filesystem Crawler Jobs
  • -
-
-
-

Overview

-
    -
  • File Collection
  • -
-
- -

-

-

-
-
-
- - Filesystem Crawl Settings - -
- Directories to crawl - - - - - - - - - -
- -

- please use ';' as delimiters -

-

- you can also use patterns like regular expressions -

-
- -
- -

- please use ';' as delimiters -

-

- you can also use patterns like regular expressions -

-
- -
-
-
- - Filter criteria patterns - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- -

- no size means that transferred files might be very large! -

-
- -
- - - -
- - - -
- - - -
- - - -
-
-
- - Job Interval - - - - - - - - - - -
- - - -
- - - -
-
- -
-
-
-
-
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/footer.html b/agent/ui/templates/footer.html deleted file mode 100644 index 368fbcb..0000000 --- a/agent/ui/templates/footer.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/agent/ui/templates/header.html b/agent/ui/templates/header.html deleted file mode 100644 index a3c209d..0000000 --- a/agent/ui/templates/header.html +++ /dev/null @@ -1,22 +0,0 @@ - - loops Agent - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/agent/ui/templates/jobdetail.html b/agent/ui/templates/jobdetail.html deleted file mode 100644 index 70c298e..0000000 --- a/agent/ui/templates/jobdetail.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent Job overview

- -
    -
  • Here you will get an overview of the current jobs registered in the agent system
  • -
-
-
-

Overview

- -
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/joblisting.html b/agent/ui/templates/joblisting.html deleted file mode 100644 index cf86cd7..0000000 --- a/agent/ui/templates/joblisting.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent Job overview

- -
    -
  • Here you will get an overview of the current jobs registered in the agent system
  • -
-
-
-

Overview

-
    -
  • Current jobs
  • - - - - - - - - - - - -

    -

    - -
    - PID - - State - - Interval - - Search Criteria - - Job Scope -
    -
-
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/mail_detailed.html b/agent/ui/templates/mail_detailed.html deleted file mode 100644 index 482222d..0000000 --- a/agent/ui/templates/mail_detailed.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent: collected Outlook Mails

- -
    -
  • All currently available Outlook Mails collected by the loops agent
  • -
-
-
-

Overview

-
    -
  • Mail in Detail
  • -
-
-
- - - - - - - - -

-

- -
- Field - - Value -
-
-
- [back to Ressources overview] -
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/mailcrawl.html b/agent/ui/templates/mailcrawl.html deleted file mode 100644 index c7201a9..0000000 --- a/agent/ui/templates/mailcrawl.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent: Create Mail Crawl Job

- -
    -
  • Configuration page for Outlook Mail Crawler jobs.
  • -
-
-
-

Overview

-
    -
  • Mail Collection
  • -
-
- -

-

-

-
-
-
- - Mail Crawl Settings - -
- Folders to crawl - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
-
-
- - Filter criteria patterns - - - - - - - - - - -
- - - -
- - - -
-
-
- - Message parts to be stored - - - - - - - - - - -
- - - -
- - - -
-
-
- - Job Interval - - - - - - - - - - -
- - - -
- - - -
-
- -
-
-
-
-
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/navigation.html b/agent/ui/templates/navigation.html deleted file mode 100644 index 5b899de..0000000 --- a/agent/ui/templates/navigation.html +++ /dev/null @@ -1,41 +0,0 @@ -
-

Navigation

- -
\ No newline at end of file diff --git a/agent/ui/templates/ressourceview.html b/agent/ui/templates/ressourceview.html deleted file mode 100644 index 0de0f0a..0000000 --- a/agent/ui/templates/ressourceview.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - -
-
-
- - - -
-
-
- -
-
-

-
-
-

Agent: collected ressources

- -
    -
  • All currently available objects that were collected by loops jobs
  • -
-
-
-

Overview

-
    -
  • Ressource Collection
  • -
-
-
- - -

-

- - -

-

- -
-
-
-
-
- -
-
- -
-
- -
- - - - \ No newline at end of file diff --git a/agent/ui/templates/top.html b/agent/ui/templates/top.html deleted file mode 100644 index b7fe491..0000000 --- a/agent/ui/templates/top.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
- -
-
\ No newline at end of file diff --git a/agent/ui/twistd.py b/agent/ui/twistd.py deleted file mode 100644 index 3be2802..0000000 --- a/agent/ui/twistd.py +++ /dev/null @@ -1,21 +0,0 @@ -#!C:\Python25\python.exe - -# Twisted, the Framework of Your Internet -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. -# See LICENSE for details. - - - -### Twisted Preamble -# This makes sure that users don't have to set up their environment -# specially in order to run these programs from bin/. -import sys, os, string -if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1: - sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir))) -if hasattr(os, "getuid") and os.getuid() != 0: - sys.path.insert(0, os.path.abspath(os.getcwd())) -### end of preamble - - -from twisted.scripts.twistd import run -run() diff --git a/agent/ui/usermode.ini b/agent/ui/usermode.ini deleted file mode 100644 index c6aee86..0000000 --- a/agent/ui/usermode.ini +++ /dev/null @@ -1 +0,0 @@ -UserMode:Simple \ No newline at end of file diff --git a/agent/ui/web.py b/agent/ui/web.py deleted file mode 100644 index a4a938d..0000000 --- a/agent/ui/web.py +++ /dev/null @@ -1,1059 +0,0 @@ -#------------------------------------------------------ -# AgentHome.py -# source file for agent UI -# author: Juergen Menzinger -# version: 0.1 -#------------------------------------------------------ - -""" - -$Id$ -""" - -import sys -import os -import tempfile -import cPickle -import traceback -import time -import email -try: - import _winreg as winreg - USE_WINDOWS = True -except ImportError: - USE_WINDOWS = False - -from nevow import loaders, rend, static, url, inevow, tags -from nevow.inevow import IRequest -from twisted.internet import defer - -from loops.agent import core -if USE_WINDOWS: - from loops.agent.crawl import outlook - from loops.agent.crawl.outlook import OutlookResource -from loops.agent.crawl.filesystem import FileResource - -# ---- global definitions and settings --------------------------------- -os.environ['HOME'] = os.path.dirname(core.__file__) -OUTLOOK2000 = 1 -PICKLE_MAILS = 0 -DEBUG = 1 -resourcesDirectory = 'resources' -templatesDirectory = 'templates' - -# some constatnts -_REG_KEY_CONST_ = "SOFTWARE\\Classes\\mailto\\shell\\open\\command" -_OUTLOOK_2007_ = "Office12" -_OUTLOOK_2003_ = "Office11" -_OUTLOOK_2000_ = "Office10" -_OUTLOOK_EXPRESS_ = "Office10" -#---------------------------------------------------------- - - -def template(fn): - return loaders.xmlfile(os.path.join( - os.path.dirname(__file__), templatesDirectory, fn)) - - -def getConfigIndex(agentobj, index_name, default_value, fn=None): - """get the number of jobs currently in config file""" - index = len(agentobj.config.crawl) - return index - - -def getOutlookVersion(): - """ - checks what outlook version we have to handle - - Returns the standard email application on this machine. - - Therefor we have to read out the registry key - "HKEY_LOCAL_MACHINE\SOFTWARE\Classes\mailto\shell\open\command" - - --ATTENTION--: - This was tested with Outlook 2007 only. - Therefor it is possible that there are problems - with some constants defined on the beginning. - - TODO: -> check this function with some other - Outlook versions installed and modify, - if needed the defined constants and return - values of the getOutlookVersion function - - """ - # open registry key - try: - key = winreg.OpenKey( - winreg.HKEY_LOCAL_MACHINE, - _REG_KEY_CONST_ ) - - except WindowsError: - return None - - try: - try: - # read out current standard outlook version - version = winreg.QueryValueEx(key, "")[0] - # check what outlook we have - if version: - if _OUTLOOK_2007_ in version: - version = _OUTLOOK_2007_ - - elif _OUTLOOK_2003_ in version: - version = _OUTLOOK_2003_ - - elif _OUTLOOK_EXPRESS_ in version: - version = _OUTLOOK_EXPRESS_ - - except WindowsError: - version = "" - finally: - # close key - winreg.CloseKey(key) - - #print '--> getOutlookVersion(): Outlook version found, version is: ', version - # return key value - return version - - -# AgentHome -# root page of the Agent UI - -class AgentHome(rend.Page): - - """Main class that builds the root ressource for the loops agent ui. - - Instance variables: - usermode -- can be 'Simple' or 'Advanced' - agent -- agent object where config and tempdir attributes can be accessed - - Class methods: - At the moment all methods of this class except the __init__ although - public, are intended to be called directly or stand-alone. - - __init__ -- Load the initial start-up settings from config - - """ - - child_resources = static.File(os.path.join( - os.path.dirname(__file__),resourcesDirectory)) - docFactory = template('agent.html') - - def __init__(self, agent=None, first_start=1): - """ Initialize the AgentHome object. - - If first_start = 1 then the initalization procedure is run, which - mainly loads the stored settings from the agent ini-file into instance - variables of the class. - - Keyword arguments: - first_start -- defaults to 1 - - """ - print "[PAGECLASS] AgentHome" - if first_start == 1: - self.agent = core.Agent() - self.usermode = self.agent.config.ui.web.setdefault('usermode', 'Simple') - print "[AgentHome] setting self.usermode: ", self.agent.config.ui.web.usermode - else: - self.agent = agent - - """ - def locateChild(self, ctx, segments): - return self, () - """ - - # calls to child pages - - def child_joboverview(self, context): - """ User requested page from menue: 'job overview' """ - return JobOverView(self.agent) - - def child_collectOutlookMails(self, context): - """Display page for starting Outlook Crawler""" - return AgentOutlookMailCrawl(self.agent) - - def child_collectFilesystem(self, context): - """Display page for starting Filesystem Crawler""" - return AgentFilesystemCrawl(self.agent) - - def child_viewRessources(self, context): - """Display page that shows all currently collected Outlook Mails.""" - return RessourceView(self.agent) - - # "Startpage" methods (class AgentHome) - - def child_changeUserMode(self, context): - """Change user mode. - - User has klicked the Change User Mode button, so - change UserMode from Simple <-> Professional - - """ - if DEBUG: - print "[child_changeUserMode] UserMode: ", self.agent.config.ui.web.usermode - form = IRequest(context).args - if form != {}: - for elem in form: - print "[child_changeUserMode] ", form[elem] - if self.agent.config.ui.web.usermode == "Simple": - self.agent.config.ui.web.usermode = "Advanced" - else: - self.agent.config.ui.web.usermode = "Simple" - self.agent.config.save() - if DEBUG: - print "====> changed UserMode: ", self.agent.config.ui.web.usermode - return AgentHome(self.agent, first_start=0) - - # "job overview" methods (class JobOverView) - - def child_ViewJobDetails(self, context): - """Get details for the selected job. - - Reads all information about the selected job from file/ database. - Returns page object which displays the available information. - - """ - selected_job = ((IRequest(context).uri).split("?"))[1] - crawl_index = selected_job.split(".")[1] - - return JobOverViewDetails(self.agent, crawl_index) - - # "add outlook crawl job" methods (class AgentOutlookMailCrawl) - - def child_submitOutlookCrawlJob(self,context): - """Initiate Outlook crawling job as requested by user.""" - ag = self.agent - conf = ag.config - crawlSubfolder = False - form = IRequest(context).args - if form != {}: - index = getConfigIndex(self.agent, 'type', 'unused') - #save job configuration - if form['mailCrawlInterval'][0] == "oneTime": - conf.crawl[index].state = 'completed' - else: - conf.crawl[index].state = 'active' - conf.crawl[index].jobid = 'outlook.%i' %(index) - conf.crawl[index].type = 'OutlookMail' - if form.has_key('inbox'): - conf.crawl[index].getinbox = '%s'%(str(form["inbox"][0])) - else: - conf.crawl[index].getinbox = 'False' - if form.has_key('subfolders'): - conf.crawl[index].getsubfolder = '%s'%(str(form["subfolders"][0])) - else: - conf.crawl[index].getsubfolder = 'False' - conf.crawl[index].subfolderpat = '%s'%(str(form["pattern"][0])) - conf.crawl[index].latest = '%i'%(index) - #TODO: how to find out what the most actual element is? - conf.crawl[index].filter_criteria = '%s'%(form["selectFilterCriteria"][0]) - conf.crawl[index].filter_pattern = '%s'%(form["filterPattern"][0]) - conf.crawl[index].content_format = '%s'%(form["selectContentFormat"][0]) - conf.crawl[index].include_attachements = '%s'%(form["selectAttachements"][0]) - conf.crawl[index].interval = '%s'%(form["mailCrawlInterval"][0]) - conf.save() - if form["mailCrawlInterval"][0] == 'oneTime': - # get version of standard mail client - outlookVersion = getOutlookVersion() - # set properties depend on version - if outlookVersion == _OUTLOOK_2000_: - fieldsMail = ['Body', - 'HTMLBody', - 'CC', - 'SenderName', - 'Recipients', - 'To', - 'Attachments', - 'Subject' - ] - elif outlookVersion == _OUTLOOK_2007_: - fieldsMail = ['Body', - 'BodyFormat', - 'HTMLBody', - 'CC', - 'SenderEmailAddress', - 'Recipients', - 'To', - 'Attachments', - 'Subject', - 'ReceivedTime' - ] - - # all form fileds in the html template are named according to - # the dictionary name if expected to be in params, - # this way it is not necessary to alter the code if another - # parameter is added - params = {} - for elem in form.items(): - params[elem[0]] = elem[1][0] - outlook_job = outlook.CrawlingJob() - outlook_job.params = params - deferred = outlook_job.collect() - deferred.addCallback(self.defMailCrawl, index, params, context) - deferred.addErrback(self.defMailCrawlError,context) - return deferred - else: - #TODO implement forwarding to next form (scheduler) - return AgentOutlookMailCrawl(self.agent, "Scheduled Mail Crawling not implemented yet.") - - else: - return AgentOutlookMailCrawl(self.agent, "An error occurred: form data has been empty.") - return AgentOutlookMailCrawl(self.agent, "Crawl Job settings have been saved.") - - def defMailCrawl(self, mail_collection, index, params, context): - """Save and Forward the mail collection to a page view.""" - if DEBUG: - print "====> seting and saving mails to disk" - print "====> agent base dir: ",self.agent.tempdir - tmpdir = tempfile.mkdtemp(prefix='mailJob_%i_'%(index), dir=self.agent.tempdir) - if DEBUG: - print "====> outlook job dir: ", tmpdir - files = os.listdir(tmpdir) - filenum = len(files) - for elem in mail_collection: - tmpfile = tempfile.mkstemp(prefix='%i_mail_'%(filenum), dir=tmpdir) - if PICKLE_MAILS == 0: - # write data as MIME string into a text file - os.write(tmpfile[0], elem.data.as_string()) - else: - # alternatively also writing the list as a whole might be - # more performant (less disc I/O) but how to store the - # path so that the user can have a look at the mail view - # page and the page knows in which directory to look? - # Idea could be a object that stores all current paths and - # their crawlIDs - os.write(tmpfile[0], cPickle.dumps(elem)) - os.close(tmpfile[0]) - filenum = filenum + 1 - return RessourceView(self.agent, mail_collection, "The collected mails have been saved in %s"%(tmpdir), tmpdir) - - def defMailCrawlError(self, ErrorMessage, context): - """Handles errors that ocurred in the MailCrawler.""" - #TODO: handle whole exception information - return traceback.format_exc() - - # "view collected ressources" methods (class RessourceViewView) - - def child_viewRessourceDetails(self,context): - """Get details for the selected ressource object. - - Reads all information about the selected ressource from file/ or the - mail_collection list. - Returns page object which displays the available information. - - """ - selected_item = ((IRequest(context).uri).split("?"))[1] - form = IRequest(context).args - if form != {}: - requested_file="" - if DEBUG: - print "[child_viewRessourceDetails]" - print "====> selected item: ", selected_item - print "====> printing items in directory" - fileslist = os.listdir(form['ressourceObjData'][0]) - for elem in fileslist: - if DEBUG: - print elem - if elem.startswith(("%i_mail_"%(int(selected_item))), 0): - requested_file = elem - requested_file = os.path.join(form['ressourceObjData'][0], requested_file) - if requested_file.find("mail") > 0: - fp = open(requested_file, "rb") - mail_parser = email.Parser.Parser() - mailobj = OutlookResource(mail_parser.parse(fp)) - fp.close() - mail = [] - for elem in mailobj.data.items(): - if DEBUG: - print "====> building MIMEObject" - print "====> appending attribute: ", elem - mail.append(elem) - if hasattr(mailobj.data, 'preamble'): - if DEBUG: - print "====> copying preamble contents" - mail.append(['Preamble', mailobj.data.preamble]) - else: - mail.append(['Preamble','']) - if hasattr(mailobj.data,'epilogue'): - if DEBUG: - print "====> copying epilogue contents" - mail.append(['Epilogue', mailobj.data.epilogue]) - else: - mail.append(['Epilogue', '']) - return OutlookMailDetail(agent=self.agent, pagemessage="", mail=mail, filename=requested_file) - elif requested_file.find("file") > 0: - #TODO implement file analyzing - return FileObjectDetail(agent=self.agent, pagemessage="", fileobj=fileobj, filename=requested_file) - if os.path.isdir(selected_item): - # selected item is a folder -> change to that folder - if DEBUG: - print "====> selected item is a directory! changing into directory" - return RessourceView(self.agent, ressource_collection=[], pagemessage="changed to folder %s" %(selected_item), tmpdir=selected_item) - elif os.path.isfile(selected_item): - if selected_item.find("mail") > 0: - fp = open(selected_item, "rb") - mail_parser = email.Parser.Parser() - mailobj = OutlookResource(mail_parser.parse(fp)) - fp.close() - mail = [] - for elem in mailobj.data.items(): - mail.append(elem) - if hasattr(mailobj.data,'preamble'): - mail.append(['Preamble', mailobj.data.preamble]) - else: - mail.append(['Preamble','']) - if hasattr(mailobj.data,'epilogue'): - mail.append(['Epilogue',mailobj.data.epilogue]) - else: - mail.append(['Epilogue','']) - requested_file = selected_item - return OutlookMailDetail(agent=self.agent, pagemessage="", mail=mail, filename=requested_file) - elif selected_item.find("file") > 0: - #TODO implement file analyzing - return FileObjectDetail(agent=self.agent, pagemessage="", fileobj=fileobj, filename=requested_file) - - # rendering methods of Startpage - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_getAgentVersion(self, context, data): - return "0.1 alpha" - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class FooterFragment(rend.Fragment): - docFactory = template('footer.html') - - -class NavigationFragment(rend.Fragment): - docFactory = template('navigation.html') - - -class TopFragment(rend.Fragment): - docFactory = template('top.html') - - -class HeaderFragment(rend.Fragment): - docFactory = template('header.html') - - -# subpages of AgentHome - -class JobOverView(rend.Page): - - """Builds page that lists all currently registered jobs. - - Instance variables: - agent -- agent object where config and tempdir attributes can be accessed - - Class methods: - At the moment all methods of this class except the __init__ - although public, are intended to be called directly or stand-alone. - - __init__ -- Store the initial settings retrieved from AgentHome. - - """ - - docFactory = template('joblisting.html') - - def __init__(self, agent=None): - print "[PAGECLASS] JobOverView" - self.agent = agent - - # rendering methods of job overview - - def data_displayViewForm(self, context, data): - return "Overview of all running Crawling jobs" - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_fillJobTable(self, ctx, data): - """Build table which displays all registered jobs.""" - - #---- get the registered jobs from the jobfile ---- - joblist = [] - index = 0 - self.agent.config.load() - while index < len(self.agent.config.crawl): - joblist.append(dict(self.agent.config.crawl[index].items())) - index = index + 1 - job_table = [] - for job_details in joblist: - if job_details['jobid'].split(".")[0] == "outlook": - job_table.append(tags.tr - [ - tags.td - [ - tags.a(href="ViewJobDetails?%s" %(job_details['jobid'])) - [ - tags.b - [ - "[" + job_details['jobid'] +"]" - ] - ] - ], - tags.td[job_details['state']], - tags.td[job_details['interval']], - tags.td[job_details['filter_criteria']], - tags.td["Inbox: %s, Subfolders: %s" %(job_details['getinbox'], job_details['getsubfolder'])] - ] - ) - elif job_details['jobid'].split(".")[0] == "filesystem": - job_table.append(tags.tr - [ - tags.td - [ - tags.a(href="ViewJobDetails?%s" %(job_details['jobid'])) - [ - tags.b - [ - "[" + job_details['jobid'] +"]" - ] - ] - ], - tags.td[job_details['state']], - tags.td[job_details['interval']], - tags.td[job_details['filter_criteria']], - tags.td["directories"] - ] - ) - - return job_table - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class JobOverViewDetails(rend.Page): - - """Builds page that displays detailed information about a selected job. - - Instance variables: - jobdetails -- list that contains all available job information - agent -- agent object where config and tempdir attributes can be accessed - - Class methods: - At the moment all methods of this class except the __init__ although public, - are intended to be called directly or stand-alone. - - __init__ -- Store the selected job in member variable. - - """ - - docFactory = template('jobdetail.html') - - def __init__(self, agent=None, selected_jobindex=None): - print "[PAGECLASS] JobOverViewDetails" - self.selected_index = selected_jobindex - self.agent = agent - - # rendering methods of job view details - - def data_displayViewForm(self, context, data): - return "Detailed view of crawling job." - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_displayJobDetails(self, ctx, data): - """Build the table containing information about the selected job.""" - - print "*******************************************************" - print "[render_displayJobDetails] received form: ", str(self.selected_index) - print "[render_displayJobDetails] usermode: ", self.agent.config.ui.web.usermode - print "*******************************************************" - - if self.selected_index != None: - job_detailtable = [tags.tr - [ - tags.td[tags.b[parameter[0]]], - tags.td[parameter[1]] - ] - for parameter in list(self.agent.config.crawl[int(self.selected_index)].items()) - ] - return job_detailtable - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class RessourceView(rend.Page): - - """Builds page that displays an overview of all collected mails. - - Instance variables: - page_message -- string that is displayed on the page - ressource_collection -- collection containing all currently collected ressources. - temp_dir -- actual directory whose ressources have to be displayed - agent -- the agent object which holds the config data and temp directory - - Class methods: - At the moment all methods of this class except the __init__ although public, - are intended to be called directly or stand-alone. - - __init__ -- Store the mail collection object and the pagemessage - - """ - - mail_collection = None - docFactory = template('ressourceview.html') - - def __init__(self, agent=None, ressource_collection=[], pagemessage="", tmpdir=""): - print "[PAGECLASS] RessourceView" - self.ressource_collection = ressource_collection - self.page_message = pagemessage - self.temp_dir = tmpdir - self.agent = agent - - # rendering methods of view collected ressources - - def data_displayViewForm(self, context, data): - return "View of all collected ressources." - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_systemMessage(self, context, data): - """Displays messages from the system to the user.""" - message = tags.b[self.page_message] - return message - - def render_displayRessourceHeaders(self, context, data): - """Displays the column headings according to the ressource type""" - #TODO: switch between different objecttypes - if DEBUG: - print "====> creating html columns for Ressource table" - current_dir="" - if self.temp_dir == "": - current_dir = self.agent.tempdir - else: - current_dir = self.temp_dir - if self.ressource_collection == []: - files_found = 0 - for elem in os.listdir(current_dir): - if os.path.isfile(os.path.join(current_dir,elem)): - files_found = 1 - break - if files_found == 0: - if current_dir.find("mail"): - if DEBUG: - print "====> just mail subdirs in current directory" - return [tags.tr - [ - tags.th["Directory Name"], - tags.th["Created"], - tags.th["Modified"], - tags.th["Mails inside"], - ] - ] - elif current_dir.find("file"): - if DEBUG: - print "====> just file subdirs in current directory" - return [tags.tr - [ - tags.th["Directory Name"], - tags.th["Created"], - tags.th["Modified"], - tags.th["Files inside"], - ] - ] - if files_found >= 1: - if current_dir.find("mail"): - if DEBUG: - print "====> current directory has mails" - return [tags.tr - [ - tags.th["From"], - tags.th["CC"], - tags.th["Subject"], - tags.th["Recipient"], - tags.th["Date"] - ] - ] - elif current_dir.find("file"): - if DEBUG: - print "====> current directory has files" - return [tags.tr - [ - tags.th["Filename"], - tags.th["Filesize"], - tags.th["Date created"], - tags.th["Date modified"], - tags.th["Filetype"] - ] - ] - elif isinstance(self.ressource_collection[0], FileResource): - if DEBUG: - print "====> instance is a FileResource" - return [tags.tr - [ - tags.th["Filename"], - tags.th["Filesize"], - tags.th["Date created"], - tags.th["Date modified"], - tags.th["Filetype"] - ] - ] - elif isinstance(self.ressource_collection[0], OutlookResource): - if DEBUG: - print "====> instance is a OutlookResource" - return [tags.tr - [ - tags.th["From"], - tags.th["CC"], - tags.th["Subject"], - tags.th["Recipient"], - tags.th["Date"] - ] - ] - # raise exception here? - return "could not find a matching object type" - - def render_displayRessources(self, ctx, data): - """Builds table containing all currently collected ressources.""" - if self.ressource_collection != []: - if DEBUG: - print "====> building ressource with ressource_collection" - index = 0 - ressource_table = [] - if isinstance(self.ressource_collection[0],OutlookResource): - for ressourceObject in self.ressource_collection: - ressource_table.append(tags.form(name="ressourceEntry%i"%(index), action="viewRessourceDetails?%i"%(index), method="POST")[ - tags.input(name="ressourceObjData", type="hidden", value="%s"%(self.temp_dir)), - tags.tr[ - tags.td[ - tags.a(href="javascript:document.getElementsByName('ressourceEntry%i')[0].submit()"%(index))[ - tags.b[ - "[" + str(ressourceObject.data.get('From')) +"]" #SenderName - ] - ] - ], - tags.td[ - str(ressourceObject.data.get('CC')) - ], - tags.td[ - str(ressourceObject.data.get('Subject')) - ], - tags.td[ - #TODO: proper handling of unicode characters - ressourceObject.data.get('To').encode('utf-8') - ], - tags.td['SourceFolder']]]) - index = index + 1 - return ressource_table - - if isinstance(self.ressource_collection[0],FileResource): - #TODO: implement building the table for file objects - return ressource_table - - else: - if DEBUG: - print "====> building ressource by analyzing submitted dir" - if self.temp_dir == "": - current_dir = self.agent.tempdir - else: - current_dir = self.temp_dir - ressource_files= os.listdir(current_dir) - ressource_table = [] - #check whether there are directories, files or both in the actual directory - for elem in ressource_files: - element = os.path.join(current_dir,elem) - if os.path.isdir(element): - files_in_subdir = len(os.listdir(element)) - ressource_table.append(tags.tr - [ - tags.td - [ - tags.a(href="viewRessourceDetails?%s" %(element)) - [ - tags.b - [ - "[" + str(elem) +"]" - ] - ] - ], - tags.td[time.asctime(time.localtime(os.path.getctime(element)))], - tags.td[time.asctime(time.localtime(os.path.getmtime(element)))], - tags.td[files_in_subdir] - ] - ) - - elif os.path.isfile(element): - if elem.find("file") > 0: - if DEBUG: - print "====> %s is of type crawl file" %(elem) - ressource_table.append(tags.tr - [ - tags.td - [ - tags.a(href="viewRessourceDetails?%s" %(element)) - [ - tags.b - [ - "[" + str(elem) +"]" - ] - ] - ], - tags.td[os.path.getsize(element)], - tags.td[time.asctime(time.localtime(os.path.getctime(element)))], - tags.td[time.asctime(time.localtime(os.path.getmtime(element)))], - tags.td[str(elem.split(".")[-1])] - ] - ) - elif elem.find("mail") > 0: - if DEBUG: - print "====> %s is of type mail" %(elem) - fp = open(element,"rb") - mail_parser = email.Parser.Parser() - mail = OutlookResource(mail_parser.parse(fp)) - fp.close() - mail_from = mail.data.get('From', "not available") - mail_cc = mail.data.get('CC', "not available") - mail_subject = mail.data.get('Subject', "not available") - mail_recipient = mail.data.get('TO', "not available") - mail_date = mail.data.get('SENT', "not available") - ressource_table.append(tags.tr - [ - tags.td - [ - tags.a(href="viewRessourceDetails?%s" %(element)) - [ - tags.b - [ - "[" + mail_from +"]" - ] - ] - ], - tags.td[mail_cc], - tags.td[mail_subject], - tags.td[mail_recipient], - tags.td[mail_date] - ] - ) - return ressource_table - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class AgentOutlookMailCrawl(rend.Page): - - """Builds page where an Outlook Mail Crawler can be configured and run. - - Instance variables: - page_message -- string that is displayed on the page - - Class methods: - At the moment all methods of this class except the __init__ although public, - are intended to be called directly or stand-alone. - - __init__ -- store pagemessage in member variable - - """ - - docFactory = template('mailcrawl.html') - - def __init__(self, agent=None, pagemessage=""): - print "[PAGECLASS] AgentOutlookMailCrawl" - self.page_message = pagemessage - self.agent = agent - - # rendering methods of add outlook crawl job - - def data_displayViewForm(self, context, data): - return "Configure your MailCrawl Job in the form below.\ - You then can either choose to start it as a\ - single job, or as scheduled job (further input\ - for scheduler necessary). For each new collect\ - procedure a filter will be added, so that just\ - mails received in Outlook after the recent Crawl\ - Job has been run, will be collected." - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_displayOutlookMails(self, ctx, data): - """Currently no implementation""" - return "" - - def render_systemMessage(self, context, data): - """Displays messages from the system to the user.""" - message = tags.b[self.page_message] - return message - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class AgentFilesystemCrawl(rend.Page): - - """Builds page where an Filesystem Crawler can be configured and run. - - Instance variables: - page_message -- string that is displayed on the page - - Class methods: - At the moment all methods of this class except the __init__ although public, - are intended to be called directly or stand-alone. - - __init__ -- store pagemessage in member variable - - """ - - docFactory = template('filecrawl.html') - - def __init__(self, agent=None, pagemessage=""): - print "[PAGECLASS] AgentFilesystemCrawl" - self.page_message = pagemessage - self.agent = agent - - # rendering methods of ad filesystem crawl job - - def data_displayViewForm(self, context, data): - return "Configure your FileCrawl Job in the form below.\ - You then can either choose to start it as a\ - single job, or as scheduled job (further input\ - for scheduler necessary). For each new collect\ - procedure a filter will be applied, so that just\ - new objects will be collected." - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_displayFiles(self, ctx, data): - """Currently no implementation""" - return "" - - def render_systemMessage(self, context, data): - """Displays messages from the system to the user.""" - message = tags.b[self.page_message] - return message - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] - - -class OutlookMailDetail(rend.Page): - - """Builds page that displays the selected mail in detail. - - Instance variables: - page_message -- string that is displayed on the page - mail_elements -- received mailobject as a list. - agent -- agent object that holds the config data - mail_file -- filename of the selected mail to view - - Class methods: - At the moment all methods of this class except the __init__ although public, - are intended to be called directly or stand-alone. - - __init__ -- Store the mail collection object and the pagemessage - - """ - - docFactory = template('mail_detailed.html') - - def __init__(self, agent=None, pagemessage="", mail=[], filename=""): - print "[PAGECLASS] OutlookMailDetail" - self.mail_elements = mail - self.page_message = pagemessage - self.agent = agent - self.mail_file = filename - - # rendering methods of Collect Outlook Mails - - def data_displayViewForm(self, context, data): - return (tags.b["Filename of the selected mail: "], tags.i[self.mail_file]) - - def render_getActiveUserMode(self, context, data): - return self.agent.config.ui.web.usermode - - def render_systemMessage(self, context, data): - """Displays messages from the system to the user.""" - message = tags.b[self.page_message] - return message - - def render_displayOutlookMail(self, ctx, data): - """Builds the layout for the mail.""" - - if self.mail_elements != []: - mail_view = [] - for elem in self.mail_elements: - mail_view.append(tags.tr[ - tags.td[elem[0]], - tags.td[elem[1]] - ] - ) - return mail_view - - def render_footer_fragment(self, context, data): - return context.tag[FooterFragment(data)] - - def render_navigation_fragment(self, context, data): - return context.tag[NavigationFragment(data)] - - def render_top_fragment(self, context, data): - return context.tag[TopFragment(data)] - - def render_header_fragment(self, context, data): - return context.tag[HeaderFragment(data)] -