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
							
								
								
									
										369
									
								
								agent/README.txt
									
										
									
									
									
								
							
							
						
						|  | @ -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: [<loops.agent.testing.crawl.DummyResource ...>] | ||||
|   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. | ||||
| 
 | ||||
|  | @ -1,4 +0,0 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
							
								
								
									
										138
									
								
								agent/config.py
									
										
									
									
									
								
							
							
						
						|  | @ -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))) | ||||
| 
 | ||||
|  | @ -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) | ||||
| 
 | ||||
|  | @ -1,4 +0,0 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
|  | @ -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 | ||||
| 
 | ||||
|  | @ -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') | ||||
| 
 | ||||
|  | @ -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 | ||||
| 
 | ||||
|  | @ -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    | ||||
|      | ||||
|  | @ -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)<THREAD_TIMEOUT_SECS): | ||||
|         sleep(0.2) # means that the thread hasn't launched; give it a chance | ||||
|         results=findNewTopWindows(unwantedHwnds,verbose) | ||||
|                    | ||||
|     if not results: | ||||
|         # stop the program          | ||||
|         terminateApp()     | ||||
|         raise Exception, 'Failed to find any new windows!' | ||||
|      | ||||
|     if verbose: | ||||
|         print '..%d additional (new) non-trivial window(s)' % len(results) | ||||
|       | ||||
|     if verbose: | ||||
|         for hwnd in results: | ||||
|             print '..%s: %s' % tupleHwnd(hwnd)[:2] | ||||
|      | ||||
|     return results,unwantedHwnds,appThread | ||||
|        | ||||
| if __name__=='__main__': | ||||
|     pass | ||||
|  | @ -1,61 +0,0 @@ | |||
| class OrderedDict(dict): | ||||
|     def __init__(self,dict=None): | ||||
|         """ currently, dict parameter does not work """ | ||||
|         self._keys = [] | ||||
|         dict.__init__(self,dict) | ||||
| 
 | ||||
|     def __delitem__(self, key): | ||||
|         dict.__delitem__(self, key) | ||||
|         self._keys.remove(key) | ||||
| 
 | ||||
|     def __setitem__(self, key, item): | ||||
|         dict.__setitem__(self, key, item) | ||||
|         if key not in self._keys: self._keys.append(key) | ||||
| 
 | ||||
|     def clear(self): | ||||
|         dict.clear(self) | ||||
|         self._keys = [] | ||||
| 
 | ||||
|     def copy(self): | ||||
|         dict = dict.copy(self) | ||||
|         dict._keys = self._keys[:] | ||||
|         return dict | ||||
| 
 | ||||
|     def items(self): | ||||
|         return zip(self._keys, self.values()) | ||||
| 
 | ||||
|     def keys(self): | ||||
|         return self._keys | ||||
| 
 | ||||
|     def popitem(self): | ||||
|         try: | ||||
|             key = self._keys[-1] | ||||
|         except IndexError: | ||||
|             raise KeyError('dictionary is empty') | ||||
| 
 | ||||
|         val = self[key] | ||||
|         del self[key] | ||||
| 
 | ||||
|         return (key, val) | ||||
| 
 | ||||
|     def setdefault(self, key, failobj = None): | ||||
|         dict.setdefault(self, key, failobj) | ||||
|         if key not in self._keys: self._keys.append(key) | ||||
| 
 | ||||
|     def update(self, dict): | ||||
|         dict.update(self, dict) | ||||
|         for key in dict.keys(): | ||||
|             if key not in self._keys: self._keys.append(key) | ||||
| 
 | ||||
|     def values(self): | ||||
|         return map(self.get, self._keys) | ||||
| 
 | ||||
|     def __str(self): | ||||
|         return self.__repr__() | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         itemList=[] | ||||
|         for item in self.items(): | ||||
|             itemList.append('%s: %s' % item) | ||||
| 
 | ||||
|         return '{'+', '.join(itemList)+'}' | ||||
|  | @ -1,389 +0,0 @@ | |||
| from watsup.winGuiAuto import findControl,findControls,findTopWindow, \ | ||||
|                               WinGuiAutoError,getEditText,clickButton, \ | ||||
|                               activateMenuItem,getMenuInfo, \ | ||||
|                               getListboxItems,getComboboxItems, \ | ||||
|                               selectListboxItem,selectComboboxItem,\ | ||||
|                               setEditText,getTopMenu, setCheckBox,\ | ||||
|                               getCheckBox | ||||
|                                | ||||
| from watsup.launcher import launchApp,terminateApp | ||||
| import win32gui | ||||
| import win32con | ||||
| #from watsup.Knowledge import getKnowledge | ||||
| from types import ListType,TupleType | ||||
|   | ||||
| verbose=False | ||||
| 
 | ||||
| CONTROL_MAX_WAIT_SECS=3 | ||||
| 
 | ||||
| def PLaunchApp(program,wantedText=None,wantedClass=None): | ||||
|     hwnd=launchApp(program,wantedText,wantedClass,verbose)  | ||||
|     return PWindow(hwnd=hwnd)  | ||||
|    | ||||
| class PWinControl(object):  | ||||
|     """Abstract base class for PWindows and PControls"""     | ||||
|     def __init__(self,parent): | ||||
|         self.parent=parent | ||||
|          | ||||
|     def findPControl(self,hwnd=None,wantedText=None,wantedClass=None,selectionFunction=None): | ||||
|         """Factory method returning a PControl instance, or a subclass thereof,  | ||||
|         within a PWinControl instance, | ||||
|          | ||||
|         find a unique control - raises exception if non-unique | ||||
|         """  | ||||
|          | ||||
|         # if wantedClass is not given, let's try and find out what it is; | ||||
|         # then we should be better able to assign the right PControl subclass | ||||
|         # to it | ||||
|         if wantedClass==None: | ||||
|             #find the wantedClass anyway | ||||
|             p=PControl(self.parent,hwnd,wantedText,wantedClass,selectionFunction) | ||||
|             wantedClass=p.className | ||||
|         else: | ||||
|             p=None  | ||||
|          | ||||
|         # if this is a known class, return the instance of the specific | ||||
|         # subclass of PControl                | ||||
|         if KNOWN_CLASSES.has_key(wantedClass): | ||||
|             if verbose: | ||||
|                 print KNOWN_CLASSES[wantedClass],(self.parent,hwnd,wantedText,wantedClass,selectionFunction)     | ||||
|      | ||||
|             return KNOWN_CLASSES[wantedClass](self.parent,hwnd,wantedText,wantedClass,selectionFunction)     | ||||
| 
 | ||||
|         # in all other cases, return a PControl (we may have already calculated it above) | ||||
|         if p: | ||||
|             return p  | ||||
|         else: | ||||
|             return PControl(self.parent,hwnd,wantedText,wantedClass,selectionFunction) | ||||
|   | ||||
|              | ||||
|     def findPControls(self,wantedText=None,wantedClass=None,selectionFunction=None): | ||||
|         # returns a list of PControl instances which match the criteria                 | ||||
|         hwnds=findControls(wantedText,wantedClass,selectionFunction) | ||||
|         controlList=[] | ||||
|         for hwnd in hwnds: | ||||
|             controlList.append(self.findPControl(self,hwnd=hwnd)) | ||||
|         return controlList                      | ||||
| 
 | ||||
| ##class Menu(object): | ||||
| ##    # container for menu items | ||||
| ##    def __init__(self,mwnd): | ||||
| ##        self.mwnd=mwnd | ||||
| # not sure we need this entity, as we can address MenuItems directly | ||||
|          | ||||
| class MenuItem(object): | ||||
|     def __init__(self,parentWindow,*menuItems): | ||||
|         self.parentWindow=parentWindow | ||||
|         #acceept either a tuple/list or *args-type values | ||||
|         if type(menuItems[0]) in (ListType,TupleType): | ||||
|             self.menuItemTuple=menuItems[0] | ||||
|         else: | ||||
|             self.menuItemTuple=menuItems | ||||
|          | ||||
|     def activate(self): | ||||
|         activateMenuItem(self.parentWindow.hwnd,self.menuItemTuple)         | ||||
|          | ||||
|     #------------------------------------------------------------------------------- | ||||
|     # accessors and properties | ||||
|      | ||||
|     def getInfo(self): | ||||
|         return getMenuInfo(self.parentWindow.hwnd,self.menuItemTuple) | ||||
|      | ||||
|     def getName(self): | ||||
|         return menuInfo.name | ||||
|      | ||||
|     def getItemCount(self): | ||||
|         return menuInfo.itemCount | ||||
|      | ||||
|     def getIsChecked(self): | ||||
|         return menuInfo.IsChecked | ||||
|      | ||||
|     def getIsSeparator(self): | ||||
|         return menuInfo.IsSeparator | ||||
|      | ||||
|     def getIsDisabled(self): | ||||
|         return menuInfo.IsDisabled | ||||
|      | ||||
|     def getIsGreyed(self):         | ||||
|         return menuInfo.IsGreyed   | ||||
|          | ||||
|     name=property(getName) | ||||
|     itemCount=property(getItemCount) | ||||
|     isChecked=property(getIsChecked) | ||||
|     isDisabled = property(getIsDisabled) | ||||
|     isGreyed = property(getIsGreyed) | ||||
|     isSeparator = property(getIsSeparator) | ||||
|      | ||||
|     menuInfo=property(getInfo) | ||||
|      | ||||
|     #------------------------------------------------------------------------------- | ||||
|         | ||||
|        | ||||
| class PWindowError(Exception): pass     | ||||
| class PWindow(PWinControl):   | ||||
|      | ||||
|     def __init__(self,hwnd=None,wantedText=None,wantedClass=None,selectionFunction=None,controlParameters=None): | ||||
|         PWinControl.__init__(self,self) | ||||
|          | ||||
|         if hwnd: | ||||
|             self.hwnd=hwnd | ||||
|         else:     | ||||
|             try: | ||||
|                 self.hwnd=findTopWindow(wantedText=wantedText, | ||||
|                                         wantedClass=wantedClass, | ||||
|                                         selectionFunction=selectionFunction) | ||||
|             except WinGuiAutoError,e: | ||||
|                 raise PWindowError,e | ||||
|          | ||||
|         # controlParameters is the list of dictionaries with unique | ||||
|         # definitions of the controls within this Window | ||||
|         # eg controlParameters=[{'wantedClass':'TButton','wantedText':'Button1'}, | ||||
|         #                       {'wantedClass':'TRadioButton','selectionFunction':chooseIt}] | ||||
|         self.controls=[] | ||||
|         if controlParameters<>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, | ||||
|                 | ||||
|                }        | ||||
|      | ||||
|  | @ -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)       | ||||
| 
 | ||||
|  | @ -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() | ||||
|  | @ -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)     | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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 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) | ||||
|          | ||||
|     from watsup.performance import nicePrint | ||||
|     nicePrint()         | ||||
| 
 | ||||
| if __name__=='__main__': | ||||
|     print ' please run example4a or 4b' | ||||
| 
 | ||||
|  | @ -1,3 +0,0 @@ | |||
| from example4 import main | ||||
| 
 | ||||
| main('perform/perform.exe','Performance Form 1') | ||||
|  | @ -1,3 +0,0 @@ | |||
| from example4 import main | ||||
| 
 | ||||
| main('perform/perform2.exe','Performance Form 2') | ||||
| Before Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 124 KiB | 
|  | @ -1,343 +0,0 @@ | |||
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> | ||||
| <html> | ||||
| <head> | ||||
| <title>Windows Application Test System Using Python</title> | ||||
| <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> | ||||
| <link href="../tac.css" rel="stylesheet" type="text/css"> | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
| <img src="../images/tizmoi.jpg"> | ||||
| <H2>WATSUP - Windows Application Test System Using Python</H2> | ||||
| 
 | ||||
| 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.  | ||||
| <P>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!</P> | ||||
|   | ||||
|  <p>The examples in this document assume a basic familiarity with Python.</p> | ||||
|   | ||||
| 
 | ||||
| 
 | ||||
| <H2>Functional Tests</H2> | ||||
| 
 | ||||
| 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.  | ||||
| <p> | ||||
| 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: | ||||
| <ul> | ||||
| <li>Get and set text in editable controls</li> | ||||
| <li>Edit and select items from controls supporting lists</li> | ||||
| <li>Click and double-click controls to invoke their actions</li> | ||||
| <li>Determine the state of menu items and invoke them</li> | ||||
| </ul> | ||||
| <p> 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) .</p> | ||||
|    | ||||
| <H3>Example 1 - automated writing on Notepad</H3>   | ||||
| <p>Here's a simple example of the control over applications that you can have with watsup.  | ||||
| First, launch notepad from:</p> <p>Windows Start Menu - All Programs - Accessories - Notepad</p> | ||||
| Then run the following script (<a href="code/example1.py">Example 1</a>) | ||||
| <code> | ||||
| <pre> | ||||
| 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)  | ||||
|  </pre></code>  | ||||
|  Finally, close notepad.<p></p>  | ||||
|   | ||||
|  <h3>Example 2 - testing a simple example </h3> | ||||
|   | ||||
| 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). | ||||
| 
 | ||||
| <p>Now let's script a test to automate this functionality. </p> | ||||
| 
 | ||||
| First find and launch the application.  | ||||
| Then run the following script (<a href="code/example2.py">Example 2</a>) | ||||
| <code><pre> | ||||
| 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()     | ||||
| 
 | ||||
| 
 | ||||
| </pre> | ||||
| </code> | ||||
| 
 | ||||
|   | ||||
| <h3>Example 3 - automating program launch and termination</h3> | ||||
| 
 | ||||
| <p>It's a bit tedious having to start and close the application each time.  | ||||
| <a href="code/example3.py">Example 3</a> launches the application,  | ||||
| if it isn't already running, and terminates it on  | ||||
| completion of the test</p> | ||||
| 
 | ||||
| <code><pre> | ||||
| 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)     | ||||
| 
 | ||||
| </pre></code> | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| <H3>Example 4 - finding windows and controls</H3> | ||||
| 
 | ||||
| <p>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.</p> | ||||
| 
 | ||||
| <p>In the tools directory, there's a tool - ShowWindows.bat - to assist us with this.</p> | ||||
| <img src="images/ShowWindows1.jpg" alt="Show Windows 1" /> | ||||
| 
 | ||||
| 
 | ||||
|  <p>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. </p> | ||||
| 
 | ||||
| <img src="images/ShowWindows2.jpg" alt="Show Windows 2" /> | ||||
| 
 | ||||
| 
 | ||||
| <H2>Performance Tests</H2> | ||||
| 
 | ||||
| <p>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").</p> | ||||
| 
 | ||||
| <p>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.</p> | ||||
| 
 | ||||
| <p>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. | ||||
| </p> | ||||
| 
 | ||||
| 
 | ||||
| <p>So we have <a href="code/example4a.py">Example 4a</a></p> | ||||
| <code><pre> | ||||
| from example4 import main | ||||
| 
 | ||||
| main('perform.exe','Performance Form 1') | ||||
| 
 | ||||
| </pre></code> | ||||
| <p>and <a href="code/example4b.py">Example 4b</a></p> | ||||
| <code><pre> | ||||
| from example4 import main | ||||
| 
 | ||||
| main('perform2.exe','Performance Form 2') | ||||
| 
 | ||||
| </pre></code> | ||||
| 
 | ||||
| <p>which reference <a href="code/example4.py">Example 4</a>:</p> | ||||
| <code><pre> | ||||
| 
 | ||||
| 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' | ||||
| 
 | ||||
| </pre></code> | ||||
| 
 | ||||
| <h4>Key points in example4.py</h4> | ||||
| 
 | ||||
| <p>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:</p> | ||||
| 
 | ||||
| <p>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.</p> | ||||
| 
 | ||||
| <p>So in example4.py, we see that the check method requires a 1 second acceptance. | ||||
| Hence example4a succeeds and example4b fails. | ||||
| </p> | ||||
| 
 | ||||
| <H2>Regression Testing</H2> | ||||
| 
 | ||||
| <p>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. </p> | ||||
| <p>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:  | ||||
| </p> | ||||
| <img src="images/framework1.jpg"> | ||||
| 
 | ||||
| <p>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</p> | ||||
| 
 | ||||
| <img src="images/framework2.jpg">  | ||||
| 
 | ||||
| <p>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</p> | ||||
| 
 | ||||
| <img src="images/framework3.jpg">  | ||||
| <p>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.  | ||||
| 
 | ||||
| <H2>Downloads</H2> | ||||
| 
 | ||||
| Download <a href="../downloads/watsup-0.4.zip">watsup</a> here | ||||
| <p>(This package should be unzipped in the site-packages directory in your python installation)</p> | ||||
| 
 | ||||
| <H3> Dependencies</H3> | ||||
|  <ul> | ||||
|  <li><a href="http://www.python.org/download"> Python</a> (version at least 2.3)</li> | ||||
|  <li><a href="http://sourceforge.net/projects/pywin32">pywin32 </a></li> | ||||
| <li><a href="http://sourceforge.net/project/showfiles.php?group_id=71702">ctypes </a></li> | ||||
| <li><a href="http://www.rutherfurd.net/python/sendkeys/#binaries" >SendKeys </a></li> | ||||
| <li><a href="http://www.wxpython.org/download.php" >wxPython library</a>  (for tools)</li> | ||||
|  </ul> | ||||
|   | ||||
|  <h3>Credits</h3> | ||||
|   | ||||
| <p> 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  | ||||
|   <a href="http://www.brunningonline.net/simon/python/index.html">Simon Brunning</a>,  | ||||
|   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.  | ||||
| 	</p> | ||||
| 	  | ||||
| Dr Tim Couper<br/> | ||||
| <a href="mailto:timc@tizmoi.net">timc@tizmoi.net</a> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
| Before Width: | Height: | Size: 6.7 KiB | 
|  | @ -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} | ||||
| 
 | ||||
|  | @ -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 | ||||
|  | @ -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.startTime<other.startTime: | ||||
|             return -1 | ||||
|         elif self.startTime>other.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()))    | ||||
|                       | ||||
|  | @ -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.startTime<other.startTime: | ||||
| ##            return -1 | ||||
| ##        elif self.startTime>other.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)<maxWaitSecs :  | ||||
|             try:                 | ||||
|                 func(self,*args)  | ||||
|                 break | ||||
|             except self.failureException: | ||||
|                 if pump: | ||||
|                     pump() | ||||
|                  | ||||
|                 sleep(retrySecs) | ||||
|                  | ||||
|         else: | ||||
|             # timed out, and still not worked! | ||||
| 
 | ||||
|             try:                 | ||||
|                 func(self,*args)  | ||||
|             except self.failureException,e: | ||||
|                 raise TimedOutError,e     | ||||
|       | ||||
|     def fail(self,msg=None,maxWaitSecs=None,retrySecs=None): | ||||
|         self.doTimeTest(TestCase.fail,maxWaitSecs,retrySecs) | ||||
|                          | ||||
|     def failIf(self, expr, msg=None,maxWaitSecs=None,retrySecs=None): | ||||
|         "Fail the test if the expression is true." | ||||
|         self.doTimeTest(TestCase.failIf,expr,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     def failUnless(self, expr, msg=None,maxWaitSecs=None,retrySecs=None): | ||||
|         """Fail the test unless the expression is true.""" | ||||
|         self.doTimeTest(TestCase.failUnless,expr,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     def failUnlessEqual(self, first, second, msg=None,maxWaitSecs=None,retrySecs=None): | ||||
|         """Fail if the two objects are unequal as determined by the '==' | ||||
|            operator. | ||||
|         """ | ||||
|         self.doTimeTest(TestCase.failUnlessEqual,first,second,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     def failIfEqual(self, first, second, msg=None): | ||||
|         """Fail if the two objects are equal as determined by the '==' | ||||
|            operator. | ||||
|         """ | ||||
|         self.doTimeTest(TestCase.failIfEqual,first,second,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     def failUnlessAlmostEqual(self, first, second, places=7, msg=None): | ||||
|         """Fail if the two objects are unequal as determined by their | ||||
|            difference rounded to the given number of decimal places | ||||
|            (default 7) and comparing to zero. | ||||
| 
 | ||||
|            Note that decimal places (from zero) is usually not the same | ||||
|            as significant digits (measured from the most signficant digit). | ||||
|         """ | ||||
|         self.doTimeTest(TestCase.failUnlessAlmostEqual,first,second,places,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     def failIfAlmostEqual(self, first, second, places=7, msg=None): | ||||
|         """Fail if the two objects are equal as determined by their | ||||
|            difference rounded to the given number of decimal places | ||||
|            (default 7) and comparing to zero. | ||||
| 
 | ||||
|            Note that decimal places (from zero) is usually not the same | ||||
|            as significant digits (measured from the most signficant digit). | ||||
|         """ | ||||
|         self.doTimeTest(TestCase.failIfAlmostEqual,first,second,places,msg,maxWaitSecs,retrySecs) | ||||
| 
 | ||||
|     assertEqual = assertEquals = failUnlessEqual | ||||
| 
 | ||||
|     assertNotEqual = assertNotEquals = failIfEqual | ||||
| 
 | ||||
|     assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual | ||||
| 
 | ||||
|     assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual | ||||
| 
 | ||||
|     assert_ = failUnless | ||||
| 
 | ||||
|  | @ -1 +0,0 @@ | |||
| pythonw wxShowWindows.py | ||||
|  | @ -1,96 +0,0 @@ | |||
| import unittest | ||||
| import os.path, imp, os | ||||
| from types import ListType, TupleType | ||||
| 
 | ||||
| def buildSuite(paths,recurse_directories=False): | ||||
|     # returns a suite of TestCase classes from the list of paths, | ||||
|     # These may be files and directories. If directories, | ||||
|     # there's the opportunity to recurse down all | ||||
|      | ||||
|     def visit(args,dirname,allnames): | ||||
|          | ||||
|         for aname in allnames: | ||||
|             fullname=os.path.join(dirname,aname) | ||||
|             if os.path.isdir(fullname):                 | ||||
|                 res=buildSuitesFromDirectory(fullname) | ||||
|                 if res: | ||||
|                     suites.extend(res)    | ||||
|          | ||||
|     #ensure paths is a list: | ||||
|     if type(paths) not in (ListType, TupleType): | ||||
|         paths=[paths] | ||||
|      | ||||
|     suites=[]         | ||||
|     for path in paths: | ||||
|         if os.path.isfile(path): | ||||
|             res=buildSuiteFromFile(path) | ||||
|             if res: | ||||
|                 suites.append(res) | ||||
|         elif os.path.isdir(path):  | ||||
|             #print 'Directory: %s' % path   | ||||
|             # find all the TestCases in this directory: | ||||
|             res=buildSuitesFromDirectory(path) | ||||
|             if res: | ||||
|                suites.extend(res) | ||||
|                      | ||||
|             if recurse_directories: | ||||
|                 os.path.walk(path,visit,suites) | ||||
|      | ||||
|     t=unittest.TestSuite()   | ||||
|     for suite in suites:         | ||||
|         for test in suite._tests: | ||||
|             t.addTest(test) | ||||
|                      | ||||
|     if len(t._tests): | ||||
|         return t           | ||||
|     else: | ||||
|         return None | ||||
|      | ||||
|                      | ||||
| def buildSuiteFromFile(filename):    | ||||
|     modname, modtype = os.path.splitext(os.path.basename(filename))   | ||||
|      | ||||
|     if modtype.lower() == '.py':       | ||||
|         moduleToTest = imp.load_source(modname, filename, file(filename)) | ||||
|     #elif modtype.lower() in {'.pyc':0, '.pyo':0}: | ||||
|     #    moduleToTest = imp.load_compiled(modname, filename, file(filename, 'rb')) | ||||
|     else: | ||||
|         return None | ||||
|          | ||||
|     suite= unittest.defaultTestLoader.loadTestsFromModule(moduleToTest) | ||||
|     for test in suite._tests: # tests is a TestSuite class | ||||
|         # remove any which are null: | ||||
|         if len(test._tests)==0: | ||||
|             suite._tests.remove(test) | ||||
|       | ||||
|     if len(suite._tests): #not interested if no tests! | ||||
|         return suite | ||||
|     else: | ||||
|         return None | ||||
|         | ||||
| def buildSuitesFromDirectory(dirname): | ||||
|     filenames=os.listdir(dirname) | ||||
|     suites=[] | ||||
|     for filename in filenames: | ||||
|         fullname=os.path.join(dirname,filename) | ||||
|         res=buildSuiteFromFile(fullname) | ||||
|         if res:             | ||||
|             suites.append(res) | ||||
|              | ||||
|     return suites | ||||
| 
 | ||||
| if __name__=='__main__': | ||||
|      | ||||
|      dir='c:/MyProducts/watsup/examples/unittests' | ||||
|      fs=[] | ||||
|      for fname in ('ExampleTestsAgain.py','ExampleTests.py'): | ||||
|          fs.append(os.path.join(dir,fname)) | ||||
|      myfile=os.path.abspath( __file__)     | ||||
|      #print buildSuite(myfile) | ||||
|      mydir=os.path.split(myfile)[0] | ||||
|      suite= buildSuite(dir,True) | ||||
|      print '------------' | ||||
|      print suite._tests | ||||
|           | ||||
|      | ||||
|      | ||||
|  | @ -1,42 +0,0 @@ | |||
| """ Lists the windowText and className for a given executable on the system.  | ||||
| Usage: | ||||
|      | ||||
| c:>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 <path_to_executable>' | ||||
| 
 | ||||
| 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]) | ||||
| 
 | ||||
|  | @ -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 | ||||
|       | ||||
|  | @ -1,4 +0,0 @@ | |||
| [main] | ||||
| checking = True | ||||
| current_directory = C:\Python23\Lib\site-packages\watsup\examples\unittests | ||||
| 
 | ||||
|  | @ -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) | ||||
|  | @ -1 +0,0 @@ | |||
| python watsup_framework.py | ||||
|  | @ -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 | ||||
| 
 | ||||
|  | @ -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() | ||||
|  | @ -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 | ||||
|  | @ -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)     | ||||
|      | ||||
|              | ||||
|  | @ -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. | ||||
|         """ | ||||
| 
 | ||||
							
								
								
									
										76
									
								
								agent/log.py
									
										
									
									
									
								
							
							
						
						|  | @ -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)) | ||||
| 
 | ||||
|  | @ -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) | ||||
| 
 | ||||
|  | @ -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.') | ||||
| 
 | ||||
|  | @ -1,4 +0,0 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
|  | @ -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.' | ||||
|  | @ -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 | ||||
|  | @ -1 +0,0 @@ | |||
| Data from file1.txt | ||||
|  | @ -1 +0,0 @@ | |||
| Data from file2.txt | ||||
|  | @ -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('<h1>Hello World</h1>') | ||||
|             self.write('<p>dir(self): %s</p>' % dir(self)) | ||||
|             self.write('<p>self.path: %s</p>' % self.path) | ||||
|             self.write('<p>self.uri: %s</p>' % self.uri) | ||||
|             self.write('<p>self.args: %s</p>' % 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) | ||||
|  | @ -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' | ||||
|  | @ -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') | ||||
| 
 | ||||
|  | @ -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') | ||||
|  | @ -1,4 +0,0 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
|  | @ -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 | ||||
| 
 | ||||
|  | @ -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() | ||||
| 
 | ||||
|  | @ -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) | ||||
| 
 | ||||
|  | @ -1,4 +0,0 @@ | |||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
|  | @ -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) | ||||
|  | @ -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; } | ||||
| 
 | ||||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
| Before Width: | Height: | Size: 580 B | 
|  | @ -1,138 +0,0 @@ | |||
| /* | ||||
|  $Id$ | ||||
| 
 | ||||
|  settings specific for view / node objects | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| a[href]:hover { | ||||
|     text-decoration: none; | ||||
|     color: #803000; | ||||
| } | ||||
| 
 | ||||
| pre { | ||||
|     overflow: scroll; | ||||
|     max-height: 35em; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.even { | ||||
|     background-color: #f4f4f4; | ||||
| } | ||||
| 
 | ||||
| .box { | ||||
|     margin: 12px; | ||||
| } | ||||
| 
 | ||||
| #body { | ||||
|     margin-left: 5px; | ||||
| } | ||||
| 
 | ||||
| /*.content-1 h1 { */ | ||||
| h1 { | ||||
|     font-size: 160%; | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .content-2 h1, h2 { | ||||
|     font-size: 140%; | ||||
| } | ||||
| 
 | ||||
| .content-3 h1, .content-2 h2, h3 { | ||||
|     font-size: 130%; | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4 { | ||||
|     font-size: 120%; | ||||
| } | ||||
| 
 | ||||
| .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 { | ||||
|     font-size: 100%; | ||||
|     border: none; | ||||
| } | ||||
| 
 | ||||
| .subcolumn { | ||||
|     display: inline; | ||||
|     float: left; | ||||
| } | ||||
| 
 | ||||
| .box { | ||||
|     margin: 5px; | ||||
|     padding: 6px; | ||||
|     padding-top: 0; | ||||
| } | ||||
| 
 | ||||
| .box h1, .box h2, .box h3 { | ||||
|     border-bottom: None; | ||||
| } | ||||
| 
 | ||||
| div.menu-1, div.menu-2 { | ||||
|     border-top: 1px solid #eeeeee; | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.menu-3 { | ||||
|     border-top: none; | ||||
|     padding-left: 1.5em; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.menu-4 { | ||||
|     padding-left: 3em; | ||||
|     font-size: 90% | ||||
| } | ||||
| 
 | ||||
| .flow-left { | ||||
|     float: left; | ||||
| } | ||||
| 
 | ||||
| .flow-right { | ||||
|     float: right; | ||||
| } | ||||
| 
 | ||||
| div.image { | ||||
|     margin-top: 10px; | ||||
|     margin-right: 5px; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* search stuff */ | ||||
| 
 | ||||
| .searchForm input.button, input.submit { | ||||
|     padding: 2px; | ||||
| } | ||||
| 
 | ||||
| .searchForm input.submit { | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| /* dojo stuff */ | ||||
| 
 | ||||
| /*.dojoComboBox { | ||||
|     width: 200px; | ||||
| }*/ | ||||
| 
 | ||||
| .dojoDialog { | ||||
|     background: #eee; | ||||
|     border: 1px solid #999; | ||||
|     -moz-border-radius: 5px; | ||||
|     padding: 4px; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog th { | ||||
|     font-size: 120%; | ||||
|     padding: 0 5px 8px 5px; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog .headline { | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog input.text { | ||||
|     width: 100%; | ||||
|     margin-right: 10px; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog input.submit { | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
|  | @ -1,126 +0,0 @@ | |||
| /* $Id: loops.js 1667 2007-03-26 10:43:15Z helmutm $ */ | ||||
| 
 | ||||
| function openEditWindow(url) { | ||||
|     zmi = window.open(url, 'zmi'); | ||||
|     zmi.focus(); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| function focusOpener() { | ||||
|     if (typeof(opener) != 'undefined' && opener != null) { | ||||
|         opener.location.reload(); | ||||
|         opener.focus(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function replaceFieldsNode(targetId, typeId, url) { | ||||
|     token = dojo.byId(typeId).value; | ||||
|     uri = url + '?form.type=' + token; | ||||
|     dojo.io.updateNode(targetId, uri); | ||||
| } | ||||
| 
 | ||||
| function submitReplacing(targetId, formId, actionUrl) { | ||||
|     dojo.io.updateNode(targetId, { | ||||
|             url: actionUrl, | ||||
|             formNode: dojo.byId(formId), | ||||
|             method: 'post' | ||||
|         }); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| function inlineEdit(id, saveUrl) { | ||||
|     var iconNode = dojo.byId('inlineedit_icon'); | ||||
|     iconNode.style.visibility = 'hidden'; | ||||
|     editor = dojo.widget.createWidget('Editor', | ||||
|         {items: ['save', '|', 'formatblock', '|', | ||||
|                  'insertunorderedlist', 'insertorderedlist', '|', | ||||
|                  'bold', 'italic', '|', 'createLink', 'insertimage'], | ||||
|          saveUrl: saveUrl, | ||||
|          //closeOnSave: true,
 | ||||
|          htmlEditing: true | ||||
|         //onClose: function() {
 | ||||
|         /* onSave: function() { | ||||
|             this.disableToolbar(true); | ||||
|             iconNode.style.visibility = 'visible'; | ||||
|             //window.location.reload();
 | ||||
|          }*/ | ||||
|     }, dojo.byId(id)); | ||||
|     editor._save = function (e) { | ||||
|             if (!this._richText.isClosed) { | ||||
|                 if (this.saveUrl.length) { | ||||
|                     var content = {}; | ||||
|                     this._richText.contentFilters = []; | ||||
|                     content[this.saveArgName] = this.getHtml(); | ||||
|                     content['version'] = 'this'; | ||||
|                     dojo.io.bind({method:this.saveMethod, | ||||
|                                   url:this.saveUrl, | ||||
|                                   content:content, | ||||
|                                   handle:function(type, data, ti, kwargs) { | ||||
|                                     location.reload(false); | ||||
|                                   } | ||||
|                                  }); //alert('save');
 | ||||
|                 } else { | ||||
|                     dojo.debug("please set a saveUrl for the editor"); | ||||
|                 } | ||||
|                 if (this.closeOnSave) { | ||||
|                     this._richText.close(e.getName().toLowerCase() == "save"); | ||||
|                 } | ||||
|             } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| function setConceptTypeForComboBox(typeId, cbId) { | ||||
|     var t = dojo.byId(typeId).value; | ||||
|     var cb = dojo.widget.manager.getWidgetById(cbId) | ||||
|     var dp = cb.dataProvider; | ||||
|     var baseUrl = dp.searchUrl.split('&')[0]; | ||||
|     var newUrl = baseUrl + '&searchType=' + t; | ||||
|     dp.searchUrl = newUrl; | ||||
|     cb.setValue(''); | ||||
| } | ||||
| 
 | ||||
| var dialogs = {} | ||||
| 
 | ||||
| function objectDialog(dlgName, url) { | ||||
|     dojo.require('dojo.widget.Dialog'); | ||||
|     dojo.require('dojo.widget.ComboBox'); | ||||
|     dlg = dialogs[dlgName]; | ||||
|     if (!dlg) { | ||||
|         //dlg = dojo.widget.fromScript('Dialog',
 | ||||
|         dlg = dojo.widget.createWidget('Dialog', | ||||
|             {bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', toggleDuration: 250, | ||||
|              executeScripts: true, | ||||
|              href: url | ||||
|             }, dojo.byId('dialog.' + dlgName)); | ||||
|         dialogs[dlgName] = dlg; | ||||
|     } | ||||
|     dlg.show(); | ||||
| } | ||||
| 
 | ||||
| function addConceptAssignment() { | ||||
|     dojo.require('dojo.html') | ||||
|     node = dojo.byId('form.assignments'); | ||||
|     els = document.forms[0].elements; | ||||
|     for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE
 | ||||
|         el = els[i]; | ||||
|         if (el.name == 'concept.search.text_selected') { | ||||
|             cToken = el.value; | ||||
|         } else if (el.name == 'concept.search.text') { | ||||
|             title = el.value; | ||||
|         } | ||||
|     } | ||||
|     if (cToken.length == 0) { | ||||
|         alert('Please select a concept!'); | ||||
|         return false; | ||||
|     } | ||||
|     pToken = dojo.byId('concept.search.predicate').value; | ||||
|     token = cToken + ':' + pToken; | ||||
|     var td = document.createElement('td'); | ||||
|     td.colSpan = 5; | ||||
|     td.innerHTML = '<input type="checkbox" name="form.assignments.selected:list" value="' + token + '" checked><span>' + title + '</span>'; | ||||
|     var tr = document.createElement('tr'); | ||||
|     tr.appendChild(td); | ||||
|     node.appendChild(tr); | ||||
| } | ||||
| 
 | ||||
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 13 KiB | 
|  | @ -1,13 +0,0 @@ | |||
| /* | ||||
|  $Id$ | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| .top, #header, #menu, #sub-section, #footer { | ||||
|     display: none; | ||||
| } | ||||
| 
 | ||||
| #content { | ||||
|     width: 100%; | ||||
|     color: Black; | ||||
| } | ||||
|  | @ -1,662 +0,0 @@ | |||
| /* | ||||
| ** Zope3 style sheet for CSS2-capable browsers. | ||||
| ** For future skin see zope.app.boston. | ||||
| */ | ||||
| 
 | ||||
| /* | ||||
| * { border: 1px dotted red } | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| /* Basic Elements */ | ||||
| 
 | ||||
| body { | ||||
|     font: 85% Helvetica, Arial, sans-serif; | ||||
|     background: White; | ||||
|     color: Black; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| /* These work in IE only, changes the look of the scrollbar + textareas */ | ||||
|     scrollbar-base-color: White; | ||||
|     scrollbar-highlight-color: White; | ||||
|     scrollbar-track-color: #F8F8F8; | ||||
|     scrollbar-darkshadow-color: #F8F8F8; | ||||
|     scrollbar-3dlight-color: #369; | ||||
|     scrollbar-shadow-color: #369; | ||||
|     scrollbar-arrow-color: Black; | ||||
| } | ||||
| 
 | ||||
| table { | ||||
|     border-collapse: collapse; | ||||
|     font-size: 100%; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|     text-decoration: none; | ||||
|     color: #369; | ||||
|     background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| a[href]:active { | ||||
|     text-decoration: underline; | ||||
| } | ||||
| 
 | ||||
| img { | ||||
|     border: none; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| p { | ||||
|     margin: 0.5em 0em 1em 0em; | ||||
|     line-height: 1.5em; | ||||
| } | ||||
| 
 | ||||
| p a:visited { | ||||
|     color: Purple; | ||||
|     background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| p a:active { | ||||
|     color: Red; | ||||
|     background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| p img { | ||||
|     border: 0; | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| hr { | ||||
|     clear: both; | ||||
|     height: 1px; | ||||
|     color: #369; | ||||
|     background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| h1, h2, h3, h4, h5, h6 { | ||||
|     color: Black; | ||||
|     clear: left; | ||||
|     font: 100% bold Verdana, Helvetica, Arial, sans-serif; | ||||
|     margin: 0; | ||||
|     padding-top: 0.5em; | ||||
|     border-bottom: 1px solid #369; | ||||
| } | ||||
| 
 | ||||
| h1 { | ||||
|     font-size: 160%; | ||||
| } | ||||
| 
 | ||||
| h2 { | ||||
|     font-size: 150%; | ||||
| } | ||||
| 
 | ||||
| h3 { | ||||
|     font-size: 140%; | ||||
| } | ||||
| 
 | ||||
| h4 { | ||||
|     font-size: 120%; | ||||
| } | ||||
| 
 | ||||
| h5 { | ||||
|     font-size: 100%; | ||||
| } | ||||
| 
 | ||||
| h6 { | ||||
|     font-size: 80%; | ||||
| } | ||||
| 
 | ||||
| ul { | ||||
|     line-height: 1.5em; | ||||
|     /* list-style-image: url("bullet.gif"); */ | ||||
|     margin-left: 2em; | ||||
|     padding:0; | ||||
| } | ||||
| 
 | ||||
| ol { | ||||
|     line-height: 1.5em; | ||||
|     margin-left: 2em; | ||||
|     padding:0; | ||||
| } | ||||
| 
 | ||||
| dl { | ||||
| } | ||||
| 
 | ||||
| dt { | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| dd { | ||||
|     line-height: 1.5em; | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| fieldset { | ||||
|     border: 1px solid #A0A0A0; | ||||
|     /* | ||||
|     margin: 2em 0em 1em 0em; | ||||
|     padding: 1em 0em; | ||||
|     */ | ||||
|     margin: 0em 0em 2em 0em; | ||||
|     padding: 0 1em 1em 1em; | ||||
| } | ||||
| 
 | ||||
| legend { | ||||
|     background: White; | ||||
|     padding: 0.5em; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| form { | ||||
|     border: none; | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| textarea { | ||||
|     color: Black; | ||||
|     width: 88%; | ||||
|     padding: 0.1em; | ||||
| } | ||||
| 
 | ||||
| input { | ||||
|     font: normal 100% Verdana, Helvetica, Arial, sans-serif; | ||||
|     color: Black; | ||||
|     vertical-align: middle; | ||||
|     margin-bottom: 1px; /* IE bug fix */ | ||||
|     padding: 0.1em; | ||||
| } | ||||
| 
 | ||||
| select { | ||||
|     font: normal 100% Verdana, Helvetica, Arial, sans-serif; | ||||
|     vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| abbr, acronym, .explain { | ||||
|     border-bottom: 1px dotted Black; | ||||
|     color: Black; | ||||
|     background-color: transparent; | ||||
|     cursor: help; | ||||
| } | ||||
| 
 | ||||
| q { | ||||
|     font-family: Times, "Times New Roman", serif; | ||||
|     font-style: italic; | ||||
|     font-size: 120%; | ||||
| } | ||||
| 
 | ||||
| blockquote { | ||||
|     font-family: Times, "Times New Roman", serif; | ||||
|     font-style: italic; | ||||
|     font-size: 120%; | ||||
| } | ||||
| 
 | ||||
| code { | ||||
|     font-size: 120%; | ||||
|     color: Black; | ||||
|     background-color: #CCCCCC; | ||||
| } | ||||
| 
 | ||||
| pre { | ||||
|     font-size: 120%; | ||||
|     padding: 1em; | ||||
|     border: 1px solid #A0A0A0; | ||||
|     color: Black; | ||||
|     background-color: #CCCCCC; | ||||
| } | ||||
| 
 | ||||
| .netscape4 { | ||||
|     display: none; | ||||
| } | ||||
| 
 | ||||
| /* layout table | ||||
| */ | ||||
| 
 | ||||
| #layout { | ||||
|     width: 100%; | ||||
|     table-layout: auto; | ||||
|     font-size: 100%; | ||||
|     border-collapse: collapse; | ||||
|     padding: 0px; | ||||
|     margin: 0px; | ||||
| } | ||||
| 
 | ||||
| #layout td { | ||||
|     vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| #layout td.global { | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| #layout td.navigators { | ||||
|     width: 200px; | ||||
| } | ||||
| 
 | ||||
| #layout td.workspace { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*  Styles for xmltree | ||||
| */ | ||||
| 
 | ||||
| #navtreecontents { | ||||
|     padding-right: 35px; | ||||
| } | ||||
| 
 | ||||
| #navtreecontents a { | ||||
|        cursor: pointer; | ||||
|        height: 20px; | ||||
| } | ||||
| 
 | ||||
| #navtreecontents loading { | ||||
|        display: block; | ||||
|        padding-left: 31px; | ||||
|        height: 18px; | ||||
| } | ||||
| 
 | ||||
| #navtreecontents expand { | ||||
|        background-repeat: no-repeat; | ||||
|        padding-left: 14px; | ||||
|        display: inline; | ||||
|        cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| #navtreecontents icon { | ||||
|        background-repeat: no-repeat; | ||||
|        padding-left: 20px; | ||||
|        display: inline; | ||||
|        cursor: auto; | ||||
| } | ||||
| 
 | ||||
| #navtreecontents collection { | ||||
|        display: block; | ||||
|        margin-left: 10px; | ||||
| /*     border: red solid 1pt;  */ | ||||
|        height: auto; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Structural elements | ||||
| */ | ||||
| 
 | ||||
| #top { | ||||
|     border-bottom: 0.1em solid black; | ||||
| } | ||||
| 
 | ||||
| #top #userDetails { | ||||
|     float:right; | ||||
|     margin-top: 1.2em; | ||||
|     padding-right: 0.5em; | ||||
| } | ||||
| 
 | ||||
| div#action { | ||||
|     height: 24px; | ||||
|     width: 100%; | ||||
|     background-color: #336699; | ||||
| } | ||||
| 
 | ||||
| div#action ul { | ||||
|     line-height: 24px; | ||||
|     color: #FFF; | ||||
|     white-space: nowrap; | ||||
|     float: right; | ||||
|     margin: 0px; | ||||
|     padding: 0px 5px 0px 0px; | ||||
| } | ||||
| 
 | ||||
| div#action li { | ||||
|     list-style-type: none; | ||||
|     display: inline; | ||||
| } | ||||
| 
 | ||||
| div#action li a { | ||||
|     color: #FFF; | ||||
|     text-decoration: none; | ||||
|     border-left: 1px dashed white; | ||||
|     padding: 0px 5px; | ||||
| } | ||||
| 
 | ||||
| div#action li a:link { | ||||
|     color: #FFF; | ||||
| } | ||||
| 
 | ||||
| div#action li a:hover { | ||||
|     color: black; | ||||
|     background-color: white; | ||||
| } | ||||
| 
 | ||||
| #breadcrumbs { | ||||
|     margin: 0; | ||||
|     padding: 5px 5px 5px 5px; | ||||
| } | ||||
| 
 | ||||
| #navigation { | ||||
|     width: 200px; | ||||
|     vertical-align: top; | ||||
|     padding: 0px; | ||||
|     margin: 0px; | ||||
| } | ||||
| 
 | ||||
| #navigators { | ||||
|     padding: 10px 20px 0px 5px; | ||||
| } | ||||
| 
 | ||||
| /* slot boxes | ||||
| */ | ||||
| 
 | ||||
| div.box { | ||||
|     background: #CCCCCC; | ||||
|     border-right: 1px solid #CCCCCC; | ||||
|     border-left: 1px solid #CCCCCC; | ||||
|     margin-bottom: 10px; | ||||
|     padding: 0px; | ||||
| } | ||||
| 
 | ||||
| div.box h4 { | ||||
|     background: #CCCCCC; | ||||
|     border: 1px solid #CCCCCC; | ||||
|     border-style: solid solid none solid; | ||||
|     color: #808080; | ||||
|     padding: 0px 5px; | ||||
|     display: block; | ||||
|     height: 22px; | ||||
| } | ||||
| 
 | ||||
| .box div.body { | ||||
|     background: white; | ||||
|     border-bottom: 1px solid #CCCCCC; | ||||
| } | ||||
| 
 | ||||
| .box div.body div { | ||||
|     color: #777777; | ||||
|     padding: 2px 0px 5px 5px; | ||||
|     margin: 0px; | ||||
| } | ||||
| 
 | ||||
| .box div.treebody { | ||||
|     background: white; | ||||
|     border-bottom: 1px solid #CCCCCC; | ||||
| } | ||||
| 
 | ||||
| .box div.treebody table { | ||||
|     margin: 2px; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.tip { | ||||
|     color: #B30000; | ||||
|     padding: 2px 0px 5px 5px; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.even { | ||||
|     background: #EBEBE2; | ||||
|     padding: 4px; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.odd { | ||||
|     padding: 4px; | ||||
| } | ||||
| 
 | ||||
| .box .content { | ||||
|     padding: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .box h1, | ||||
| .box h2, | ||||
| .box h3, | ||||
| .box h4 { | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #content { | ||||
| } | ||||
| 
 | ||||
| #context_information { | ||||
|     padding-top: 1em; | ||||
|     width: 15%; | ||||
|     float: left; | ||||
|     padding-left: 0.5em; | ||||
| } | ||||
| 
 | ||||
| #workspace { | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| #helpers { | ||||
| } | ||||
| 
 | ||||
| #inspectors { | ||||
| } | ||||
| 
 | ||||
| #footer { | ||||
|     border-bottom: 1px solid black; | ||||
|     float: left; | ||||
|     clear: both; | ||||
| } | ||||
| 
 | ||||
| input.textType { | ||||
|     width: 88%; /* Same as textarea */ | ||||
| } | ||||
| 
 | ||||
| input.editcheck { | ||||
|     float:left; | ||||
|     position:relative; | ||||
|     top:1em; | ||||
| } | ||||
| 
 | ||||
| div.row { | ||||
|     padding-top: 1em; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| div.label { | ||||
|     #clear: both; | ||||
|     padding-top: 10px; | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| /* div.row div.field doesn't appear to be selecting. div.row div | ||||
|        is a workaround */ | ||||
| /* This seems to work in Firefox 1.0 and IE6. */ | ||||
| 
 | ||||
| div.row div.field { | ||||
|     clear: left; | ||||
|     padding-top: 1px; | ||||
| } | ||||
| 
 | ||||
| div.row div.label { | ||||
|     background: #369; | ||||
|     color: #fff; | ||||
|     padding: 0.1em 0.5em 0.1em 0.5em; /* Same as .itemViews */ | ||||
|     border: 1px solid #369; /* Same as .itemViews */ | ||||
|     margin: 0; | ||||
|     float: left; | ||||
|     clear: both; | ||||
| } | ||||
| 
 | ||||
| div.row span.error { | ||||
|     background: red; | ||||
|     color: white; | ||||
|     padding: 0.1em 0.5em 0.1em 0.5em; /* Same as .itemViews */ | ||||
|     border: 1px solid red; /* Same as .itemViews */ | ||||
|     margin: 0; | ||||
|     float: left; | ||||
|     clear: both; | ||||
| } | ||||
| /* | ||||
| div.row div.error:before { | ||||
|     content: "\2190  "; | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| #metadata .label { | ||||
|     font-size: 80%; | ||||
| } | ||||
| 
 | ||||
| .itemViews { | ||||
|     background: transparent; | ||||
|     border-collapse: collapse; | ||||
|     border-bottom: 1px solid #369; | ||||
|     padding-top: 1px; | ||||
|     padding-bottom: 1px; | ||||
|     padding-left: 1em; | ||||
|     margin-top: 0.8em; | ||||
|     white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .itemViews a { | ||||
|     background: transparent; | ||||
|     border: 1px solid #369; | ||||
|     color: Black; | ||||
|     font-weight: normal; | ||||
|     margin-right: 0.5em; | ||||
|     padding: 0.1em 0.5em 0.1em 0.5em; | ||||
| } | ||||
| 
 | ||||
| .itemViews a.selected { | ||||
|     background: #369; | ||||
|     border-bottom: #369 1px solid; | ||||
|     color: White; | ||||
|     font-weight: normal; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .itemViews a:hover { | ||||
|     background-color: #369; | ||||
|     color: White; | ||||
| } | ||||
| 
 | ||||
| #viewspace { | ||||
|     border-collapse: collapse; | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription, table.listing { | ||||
|     /* The default table for document listings. Contains name, document types, modification times etc in a file-browser-like fashion */ | ||||
|     border-collapse: collapse; | ||||
|     border-left: 1px solid #CCCCCC; | ||||
|     border-bottom: 1px solid #CCCCCC; | ||||
|     margin: 1em 0em 1em 0em; | ||||
| /*    clear: both; */ | ||||
| } | ||||
| 
 | ||||
| table.listingdescription { | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription th, table.listing th { | ||||
|     background: #CCCCCC; | ||||
|     border-top: 1px solid #CCCCCC; | ||||
|     border-bottom: 1px solid #CCCCCC; | ||||
|     border-right: 1px solid #CCCCCC; | ||||
|     color: #808080; | ||||
|     font-weight: normal; | ||||
|     padding: 0em 1em 0em 1em; | ||||
|     white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription td.top, table.listing td.top { | ||||
|     border-left: 1px solid White; | ||||
|     border-top: 1px solid White ! important; | ||||
|     border-right: 1px solid White ! important; | ||||
|     text-align: right ! important; | ||||
|     padding: 0em 0em 1em 0em; | ||||
|     /* insane IE row bug workaround */ | ||||
|     position: relative; | ||||
|     left: -1px; | ||||
|     top: -1px; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription tr.odd, table.listing tr.odd { | ||||
|     /*every second line should be shaded */ | ||||
|     background: White; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription tr.even, table.listing tr.even { | ||||
|     background: #F8F8F8; | ||||
| } | ||||
| 
 | ||||
| table.listing td { | ||||
|     border-right: 1px solid #CCCCCC; | ||||
|     padding: 0em 0.3em; | ||||
|     text-align: left; | ||||
|      white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| table.listingdescription img, table.listing img { | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| table.listingdescription td { | ||||
|     border-right: 1px solid #CCCCCC; | ||||
|     padding: 5px; | ||||
|     text-align: left; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*colorize the matrix table used in grant.html*/ | ||||
| table.matrix td.default { | ||||
|     background: green; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| table.matrix td.changed { | ||||
|     background: red; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| div.spacer { | ||||
|     clear: both; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .registrationSummary { | ||||
|     margin-left: 2em; | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
| .registrationSummary .usageSummary { | ||||
|     font-weight: bold; | ||||
| } | ||||
| .registrationSummary .modificationLink { | ||||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| div.message { | ||||
|     background: #FFCE7B; | ||||
|     border: 1px solid #FFA500; | ||||
|     color: Black; | ||||
|     font: bold 80% Verdana, Helvetica, Arial, sans-serif; | ||||
|     margin: 2em 0em 1em 0em; | ||||
|     padding: 0.5em 1em; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| div.message a { | ||||
|     color: Black; | ||||
| } | ||||
| 
 | ||||
| /* Style for page error divs.  Use this for displaying errors for a | ||||
|    page as a whole. | ||||
|  */ | ||||
| div.page_error { | ||||
|     background: #FFCE7B; | ||||
|     font: bold 80% Verdana, Helvetica, Arial, sans-serif; | ||||
|     padding: 0.5em 1em; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| div.bug { | ||||
|     background: #FFCE7B; | ||||
|     border: 1px solid #FFA500; | ||||
|     color: Black; | ||||
|     font: bold 80% Verdana, Helvetica, Arial, sans-serif; | ||||
|     margin: 2em 1em 1em 0em; | ||||
|     padding: 0.5em 1em; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
|  | @ -1,70 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- StartPage for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|     <div nevow:render="top_fragment" > | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|       <div nevow:render="navigation_fragment"> | ||||
|       </div> | ||||
| 
 | ||||
|         <br/> | ||||
|         <br/> | ||||
| 
 | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|           <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
|               <p><div align="center"><a href="changeUserMode"><b>[Switch Mode]</b></a></div></p> | ||||
|           </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Startpage</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>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 | ||||
|             <i>Professional</i> and <i>Simple Mode</i>. This button is to be found on this start page only.</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="agent-version" name="agent-version">Version</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                 <li>Agent: <div nevow:render="getAgentVersion"/></li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,228 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Outlook Mails Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|     <div nevow:render="navigation_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|         <br/> | ||||
|         <br/> | ||||
| 
 | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|           <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent: Create Filesystem Crawler Job</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>Configuration page for Filesystem Crawler Jobs</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                 <li><b>File Collection</b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                 </ul> | ||||
|                 <div align="center"> | ||||
|                 <table border="0" cellpadding="10"> | ||||
|                     <p nevow:render="displayFiles"> | ||||
|                         <tr nevow:pattern="CollectedFiles" nevow:render="data"/> | ||||
|                     </p> | ||||
|                 </table> | ||||
|                 <form name="FileCrawlForm" action="submitFilesystemCrawlJob" method="POST"> | ||||
|                     <fieldset> | ||||
|                         <legend> | ||||
|                             Filesystem Crawl Settings | ||||
|                         </legend> | ||||
|                         <fieldset> | ||||
|                             <legend>Directories to crawl</legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblRecursiveDirs">Directories to crawl <b>recursively</b></label> | ||||
|                                         <p> | ||||
|                                             <i>please use ';' as delimiters</i> | ||||
|                                         </p> | ||||
|                                         <p> | ||||
|                                             <i>you can also use patterns like regular expressions </i> | ||||
|                                         </p> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <textarea name="rec_directories" id="lblRecursiveDirs" rows="10" cols="40"></textarea> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblDirs">Directories to crawl non-recursively</label> | ||||
|                                         <p> | ||||
|                                             <i>please use ';' as delimiters</i> | ||||
|                                         </p> | ||||
|                                         <p> | ||||
|                                             <i>you can also use patterns like regular expressions </i> | ||||
|                                         </p> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <textarea name="rec_directories" id="lblDirs" rows="10" cols="40"></textarea> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <fieldset> | ||||
|                             <legend> | ||||
|                                 Filter criteria patterns | ||||
|                             </legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblApplyFileSize">Apply size criteria : </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblApplyFileSize" name="applyFileSize" type="checkbox"/> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblFileSizeLimit">Collect files that are : </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblFileSizeLimit" name="selectFileSizeLimit"> | ||||
|                                             <option>greater</option> | ||||
|                                             <option>less</option> | ||||
|                                             <option>equal</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblFileSize">than (kByte): </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblFileSize" name="fileSize" | ||||
|                                          type="text" size="12" maxlength="20" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblMaximumSize">Maximum file size: </label> | ||||
|                                         <p> | ||||
|                                             <i>no size means that transferred files might be very large!</i> | ||||
|                                         </p> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblMaximumSize" name="maximumSize" | ||||
|                                          type="text" size="12" maxlength="20" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblApplyDateCriteria">Apply date criteria: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblApplyDateCriteria" name="applyDateCriteria" type="checkbox"/> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblDateCriteria">Collect files that are: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblDateCriteria" name="selectDateCriteria"> | ||||
|                                             <option>created</option> | ||||
|                                             <option>modified</option> | ||||
|                                             <option>accessed</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblTimestampCompare"></label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblTimestampCompare" name="selectTimeStampCompare"> | ||||
|                                             <option>before</option> | ||||
|                                             <option>after</option> | ||||
|                                             <option>exactly on</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblTimeStamp">Collect files that are : </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblTimeStamp" name="selectDateCriteria" type="text" size="20"/> | ||||
|                                     </td> | ||||
|                                </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <fieldset> | ||||
|                             <legend> | ||||
|                                 Job Interval | ||||
|                             </legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblmailCrawlIntervaloneTime">One Time: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input type="radio" id="lblmailCrawlIntervaloneTime" name="mailCrawlInterval" | ||||
|                                          value="oneTime" checked="checked" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblmailCrawlIntervalScheduler">Use Scheduler: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input type="radio" id="lblmailCrawlIntervalScheduler" name="mailCrawlInterval" | ||||
|                                          value="Scheduler" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <input type="submit" name="startCrawlJob" value="Save and Start" /> | ||||
|                     </fieldset> | ||||
|                 </form> | ||||
|                 <div nevow:render="systemMessage"/> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,7 +0,0 @@ | |||
| <div id="footer" class="footer" define-macro="footer" content="text/html; charset=UTF-8"> | ||||
|   © Copyright 2007, cyberconcepts IT-Consulting Dr. Helmut Merz | ||||
|   (<a href="http://loops.cy55.de/impressum">Impressum</a>)<br /> | ||||
|   Powered by <b><a href="http://www.python.org">Python</a></b> · | ||||
|   <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> · | ||||
|   <b><a href="http://loops.cy55.de/projekte/loops">loops</a></b>. | ||||
| </div> | ||||
|  | @ -1,22 +0,0 @@ | |||
| <head> | ||||
|   <title>loops Agent</title> | ||||
|   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
| 
 | ||||
|   <style type="text/css" media="all">@import url("/resources/zope3_tablelayout.css");</style> | ||||
| 
 | ||||
|   <style type="text/css" media="screen">@import url("/resources/base.css");</style> | ||||
| 
 | ||||
|   <style type="text/css" media="print">@import url("/resources/print.css");</style> | ||||
| 
 | ||||
|   <style type="text/css" media="all">@import url("/resources/loops.css");</style> | ||||
| 
 | ||||
|   <style type="text/css" media="all">@import url("/resources/custom.css");</style> | ||||
| 
 | ||||
| 
 | ||||
|   <script type="text/javascript" src="/resources/loops.js"> | ||||
|   </script> | ||||
| 
 | ||||
|   <link rel="icon" type="image/png" href="/resources/favicon.png" /> | ||||
| 
 | ||||
|   <base href="AgentStart.html"/> | ||||
| </head> | ||||
|  | @ -1,79 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Detailed Job View Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
|         <div nevow:render="navigation_fragment"> | ||||
|         </div> | ||||
|         <br/> | ||||
|         <br/> | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|               <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
|           </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|         <div></div> | ||||
|         <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent Job overview</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>Here you will get an overview of the current jobs registered in the agent system</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                     <li><b>Job details</b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                     <table class="listing"> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th> | ||||
|                                     Attribute | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     Value | ||||
|                                 </th> | ||||
|                             </tr> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             <p nevow:render="displayJobDetails"> | ||||
|                             </p> | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                     <br/> | ||||
|                     <a href="joboverview" alt="back to Job Overview Page"><b>[back to Job Overview]</b></a> | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,89 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Job Overview Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|         <div nevow:render="navigation_fragment"> | ||||
|         </div> | ||||
|         <br/> | ||||
|         <br/> | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|               <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent Job overview</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>Here you will get an overview of the current jobs registered in the agent system</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                 <li><b>Current jobs </b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                     <table class="listing"> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th> | ||||
|                                     PID | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     State | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     Interval | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     Search Criteria | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     Job Scope | ||||
|                                 </th> | ||||
|                             </tr> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             <p nevow:render="fillJobTable"> | ||||
|                             </p> | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,87 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Outlook Mails Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|     <div nevow:render="navigation_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|         <br/> | ||||
|         <br/> | ||||
| 
 | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|           <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent: collected Outlook Mails</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>All currently available Outlook Mails collected by the loops agent</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                     <li><b>Mail in Detail</b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                 </ul> | ||||
|                 <div nevow:render="systemMessage"/> | ||||
|                 <div align="center"> | ||||
|                     <table class="listing" style="width:200px"> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th> | ||||
|                                     Field | ||||
|                                 </th> | ||||
|                                 <th> | ||||
|                                     Value | ||||
|                                 </th> | ||||
|                             </tr> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             <p nevow:render="displayOutlookMail"> | ||||
|                             </p> | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                 </div> | ||||
|             <br/> | ||||
|             <a href="viewRessources" alt="back to Ressources overview"><b>[back to Ressources overview]</b></a> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,197 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Outlook Mails Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|     <div nevow:render="navigation_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|         <br/> | ||||
|         <br/> | ||||
| 
 | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|           <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent: Create Mail Crawl Job</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>Configuration page for Outlook Mail Crawler jobs.</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                 <li><b>Mail Collection</b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                 </ul> | ||||
|                 <div align="center"> | ||||
|                 <table border="0" cellpadding="10"> | ||||
|                     <p nevow:render="displayOutlookMails"> | ||||
|                         <tr nevow:pattern="OutlookMails" nevow:render="data"/> | ||||
|                     </p> | ||||
|                 </table> | ||||
|                 <form name="OutlookCrawlForm" action="submitOutlookCrawlJob" method="POST"> | ||||
|                     <fieldset> | ||||
|                         <legend> | ||||
|                             Mail Crawl Settings | ||||
|                         </legend> | ||||
|                         <fieldset> | ||||
|                             <legend>Folders to crawl</legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblCrawlInbox">Crawl inbox</label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblCrawlInbox" name="inbox" type="checkbox" | ||||
|                                          value="inbox"/><!-- outlook job param //--> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblCrawlSubfolders">Crawl subfolders</label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input type="checkbox" id="lblCrawlSubfolders" name="subfolders" | ||||
|                                          value = "subfolders"/><!-- outlook job param //--> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblSubFolderPattern">Pattern for subfolders to include</label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                     <input type="text" maxlength="20" size="16" id="lblSubFolderPattern" | ||||
|                                      name="pattern"/><!-- outlook job param //--> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <fieldset> | ||||
|                             <legend> | ||||
|                                 Filter criteria patterns | ||||
|                             </legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblselectFilterCriteria">Filter criteria: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblselectFilterCriteria" name="selectFilterCriteria"> | ||||
|                                             <option>sender</option> | ||||
|                                             <option>receiver</option> | ||||
|                                             <option>subject</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblfilterPattern">Pattern: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input id="lblfilterPattern" name="filterPattern" | ||||
|                                          type="text" size="12" maxlength="20" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <fieldset> | ||||
|                             <legend> | ||||
|                                 Message parts to be stored | ||||
|                             </legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblselectContentFormat">Transfer HTML or Plain Text: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblselectContentFormat" name="selectContentFormat"> | ||||
|                                             <option>text</option> | ||||
|                                             <option>html</option> | ||||
|                                             <option>both</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblselectAttachements">Include attachements: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <select id="lblselectAttachements" name="selectAttachements"> | ||||
|                                             <option>Yes</option> | ||||
|                                             <option>No</option> | ||||
|                                         </select> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <fieldset> | ||||
|                             <legend> | ||||
|                                 Job Interval | ||||
|                             </legend> | ||||
|                             <table align="center"> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblmailCrawlIntervaloneTime">One Time: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input type="radio" id="lblmailCrawlIntervaloneTime" name="mailCrawlInterval" | ||||
|                                          value="oneTime" checked="checked" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                                 <tr> | ||||
|                                     <td> | ||||
|                                         <label for="lblmailCrawlIntervalScheduler">Use Scheduler: </label> | ||||
|                                     </td> | ||||
|                                     <td> | ||||
|                                         <input type="radio" id="lblmailCrawlIntervalScheduler" name="mailCrawlInterval" | ||||
|                                          value="Scheduler" /> | ||||
|                                     </td> | ||||
|                                 </tr> | ||||
|                             </table> | ||||
|                         </fieldset> | ||||
|                         <input type="submit" name="startCrawlJob" value="Save and Start" /> | ||||
|                     </fieldset> | ||||
|                 </form> | ||||
|                 <div nevow:render="systemMessage"/> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,41 +0,0 @@ | |||
| <div class="box"> | ||||
|   <h4>Navigation</h4> | ||||
|   <div class="body"> | ||||
| 
 | ||||
|       <div class="content even menu-1"> | ||||
|         <a href="/" class="">Startpage</a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="content odd menu-2">Agent configuration</div> | ||||
| 
 | ||||
|       <div class="content odd menu-3"> | ||||
|         <a href="/joboverview" class=""> | ||||
|           job overview | ||||
|         </a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="content odd menu-3"> | ||||
|          <a href="/collectOutlookMails" class=""> | ||||
|             add outlook crawl job | ||||
|          </a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="content odd menu-3"> | ||||
|          <a href="/collectFilesystem" class=""> | ||||
|             add filesystem crawl job</a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="content odd menu-3"> | ||||
|          <a href="/viewRessources" class=""> | ||||
|             view collected ressources | ||||
|          </a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="content odd menu-3"> | ||||
|          <a href="/loggingoptions" class="" title=""> | ||||
|             logging options | ||||
|          </a> | ||||
|       </div> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -1,79 +0,0 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <!--<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> //--> | ||||
| <html xmlns:nevow="http://nevow.com/ns/nevow/0.1"> | ||||
| 
 | ||||
| <!-- Ressource Page for loops.agent UI    Version: 0.1 //--> | ||||
| 
 | ||||
| <nevow:invisible nevow:render="header_fragment" /> | ||||
| 
 | ||||
|   <body> | ||||
|     <div class="body"> | ||||
|         <div nevow:render="top_fragment"> | ||||
|         </div> | ||||
| 
 | ||||
|     <div id="menu"> | ||||
| 
 | ||||
|     <div nevow:render="navigation_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|         <br/> | ||||
|         <br/> | ||||
| 
 | ||||
|         <div class="box"> | ||||
|           <h4>User Mode</h4> | ||||
|           <div class="body"> | ||||
|           <b>Current Mode: </b><p nevow:render="getActiveUserMode"/> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="content"> | ||||
|     <div></div> | ||||
|     <div></div> | ||||
| 
 | ||||
|     <div class="content-1" id="2.body" ondblclick=""> | ||||
|         <div class="line-block"> | ||||
|             <div class="line"><br /></div> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|             <h3><a id="agent-ui-startpage" name="agent-ui-startpage">Agent: collected ressources</a></h3> | ||||
| 
 | ||||
|             <ul class="simple"> | ||||
|             <li>All currently available objects that were collected by loops jobs</li> | ||||
|             </ul> | ||||
|             </div> | ||||
|             <div class="section"> | ||||
|                 <h3><a id="form-overview" name="form-overview">Overview</a></h3> | ||||
|                 <ul class="simple"> | ||||
|                     <li><b>Ressource Collection</b><div nevow:render="data" nevow:data="displayViewForm"/></li> | ||||
|                 </ul> | ||||
|                 <div nevow:render="systemMessage"/> | ||||
|                 <div align="center"> | ||||
|                     <table class="listing"> | ||||
|                         <thead> | ||||
|                             <p nevow:render="displayRessourceHeaders"> | ||||
|                             </p> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             <p nevow:render="displayRessources"> | ||||
|                             </p> | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="sub-section" define-macro="sub-section"> | ||||
|     </div> | ||||
| 
 | ||||
|     <div nevow:render="footer_fragment"> | ||||
|     </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -1,5 +0,0 @@ | |||
| <div id="global"> | ||||
|    <div class="top" style="padding: 5px"> | ||||
|       <img src="/resources/loops_logo.png" /> | ||||
|    </div> | ||||
| </div> | ||||
|  | @ -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() | ||||
|  | @ -1 +0,0 @@ | |||
| UserMode:Simple | ||||
 helmutm
						helmutm