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
|