Compare commits
	
		
			1 commit
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8bcad17505 | 
					 751 changed files with 12534 additions and 2788 deletions
				
			
		
							
								
								
									
										15
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,13 +1,6 @@ | |||
| *.pyc | ||||
| *.pyo | ||||
| */ajax/dojo/dojo* | ||||
| build/ | ||||
| dist/ | ||||
| *.swp | ||||
| *.egg-info | ||||
| *.project | ||||
| *.pydevproject | ||||
| *.sublime-project | ||||
| *.sublime-workspace | ||||
| ajax/dojo/* | ||||
| .project | ||||
| .pydevproject | ||||
| .settings | ||||
| *.ropeproject | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										21
									
								
								LICENSE
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
										
									
									
									
								
							|  | @ -1,21 +0,0 @@ | |||
| The MIT License (MIT)  | ||||
| 
 | ||||
| Copyright (C) 2023 cyberconcepts.org team | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										226
									
								
								agent/README.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								agent/README.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,226 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
| Agents do some work specified by jobs, the main task being to collect | ||||
| information objects from the local machine or some external source and | ||||
| transfer them e.g. to a loops server on the same machine or another. | ||||
| 
 | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
| This package does not depend on Zope but represents a standalone application. | ||||
| 
 | ||||
| 
 | ||||
| Sub-Packages | ||||
| ============ | ||||
| 
 | ||||
| Top-level | ||||
|     Generic interfaces, ``commponents``: adapter registry, | ||||
|     ``tests`` module, this ``README.txt`` file. | ||||
| 
 | ||||
| base | ||||
|     Base and sample classes. | ||||
| 
 | ||||
| core | ||||
|     Agent and scheduling implementations. | ||||
| 
 | ||||
| control | ||||
|     Communication with an external agent control and job database application. | ||||
| 
 | ||||
| crawl | ||||
|     Scanning/crawling some system, e.g. the database of an email application, | ||||
|     the local file system, or an external document source. | ||||
| 
 | ||||
| transport | ||||
|     Transfer of information objects to agents on another machine or | ||||
|     to an information management application (e.g. loops). | ||||
| 
 | ||||
| util | ||||
|     Various utility modules, e.g. a backport of the | ||||
|     ``twisted.internet.task.coiterate()`` function from Twisted 2.5 so that | ||||
|     we can use the Twisted version coming with Zope 3.3.1 for | ||||
|     cybertools.agent. | ||||
| 
 | ||||
| All sub-packages except ``base`` depend on Twisted. | ||||
| 
 | ||||
| 
 | ||||
| Object Structure and Control Flow | ||||
| ================================= | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|                 ----------       ------------ | ||||
|   --------     |          |<---1|            | | ||||
|  | config |--> |  master  |<---2| controller | | ||||
|   --------    /|          |4--->|            | | ||||
|              /  ----------       ------------ | ||||
|             /     1  ^  2 | ||||
|            /      |  |   \       ----------- | ||||
|           /       |  |    `---->| scheduler | | ||||
|          v        v  4           ----------- | ||||
|       -----     ---------             3 | ||||
|      | log |<--|  agent  |<----------´ | ||||
|       -----     --------- | ||||
| 
 | ||||
| (1) Agent specifications control creation and setup of agents | ||||
| 
 | ||||
| (2) Job specifications control scheduling of jobs | ||||
| 
 | ||||
| (3) Scheduler triggers job execution by agent | ||||
| 
 | ||||
| (4) Results are recorded, possibly triggering further actions | ||||
| 
 | ||||
| 
 | ||||
| Basic Stuff | ||||
| =========== | ||||
| 
 | ||||
| While the real application is based on the asynchronous communication | ||||
| framework Twisted there is some basic stuff (mainly interfaces and | ||||
| base classes with basic, sample, or dummy implementations) that is | ||||
| independent of Twisted. | ||||
| 
 | ||||
| The code for this resides in in the ``base`` sub-package. | ||||
| 
 | ||||
| Master Agent and Configuration | ||||
| ------------------------------ | ||||
| 
 | ||||
| All activity is controlled by the master agent. | ||||
| 
 | ||||
| The master agent is set up according to the specifications in a | ||||
| configuration file. | ||||
| 
 | ||||
| The configuration typically provides only basic informations, e.g. about | ||||
| the controller(s) and logger(s) to be used; details on jobs and agent | ||||
| configuration are provided by the controller. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import baseDir | ||||
|   >>> import os | ||||
|   >>> configFile = open(os.path.join(baseDir, 'base', 'sample.cfg')) | ||||
| 
 | ||||
| So we are now ready to create a master agent and configure it by supplying | ||||
| the path to the configuration file. | ||||
| 
 | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(configFile) | ||||
|   Starting agent application... | ||||
|   Using controllers base.sample. | ||||
|   >>> configFile.close() | ||||
| 
 | ||||
|   >>> master.config | ||||
|   controller.names = ['base.sample'] | ||||
|   logger.name = 'default' | ||||
|   logger.standard = 30 | ||||
|   scheduler.name = 'sample' | ||||
| 
 | ||||
| Controllers | ||||
| ----------- | ||||
| 
 | ||||
| Creation of agents and scheduling of jobs is controlled by controller | ||||
| objects. These are typically associated with a sort of control storage that | ||||
| provides agent and job specifications and receives the results of job | ||||
| execution. | ||||
| 
 | ||||
|   >>> master.controllers | ||||
|   [<cybertools.agent.base.control.SampleController object ...>] | ||||
| 
 | ||||
| We make the contollers provide the specifications via the master agent's | ||||
| ``setup()`` method. | ||||
| 
 | ||||
|   >>> master.setup() | ||||
|   Starting agent application... | ||||
|   Using controllers base.sample. | ||||
| 
 | ||||
| Other Agents | ||||
| ------------ | ||||
| 
 | ||||
| The above ``setup()`` call has triggered the creation of one child agent - | ||||
| that is all the sample controller provides. | ||||
| 
 | ||||
|   >>> master.children | ||||
|   {'sample01': <cybertools.agent.base.agent.SampleAgent object ...>} | ||||
| 
 | ||||
| Let's check a few attributes of the newly created agent. | ||||
| 
 | ||||
|   >>> agent01 = master.children['sample01'] | ||||
|   >>> agent01.master is master | ||||
|   True | ||||
|   >>> agent01.config is master.config | ||||
|   True | ||||
| 
 | ||||
| Job Scheduling and Execution | ||||
| ---------------------------- | ||||
| 
 | ||||
| A scheduler is responsible for triggering the execution of a job at the | ||||
| appropriate time. The master agent schedules the jobs based upon the | ||||
| information (job specifications) it gets from the controller. There | ||||
| is just one scheduler associated with the master agent. | ||||
| 
 | ||||
|   >>> master.scheduler | ||||
|   <cybertools.agent.base.schedule.Scheduler object ...> | ||||
| 
 | ||||
| We schedule a sample job by calling an internal method of the agent's | ||||
| controller. In addition to the output of the job execution itself we | ||||
| also get a note from the controller about the feedback it received | ||||
| about the outcome of the job execution. | ||||
| 
 | ||||
|   >>> master.controllers[0].enterJob('sample', 'sample01') | ||||
|   Job 00001 on agent sample01 has been executed. | ||||
|   Job 00001 completed; result: None; | ||||
| 
 | ||||
| Logging | ||||
| ------- | ||||
| 
 | ||||
|   >>> master.logger | ||||
|   <cybertools.agent.base.log.Logger object ...> | ||||
|   >>> agent01.logger is master.logger | ||||
|   True | ||||
| 
 | ||||
|   >>> master.config.logger.standard = 20 | ||||
|   >>> master.logger.setup() | ||||
|   >>> master.controllers[0].enterJob('sample', 'sample01') | ||||
|   Job 00002 on agent sample01 has been executed. | ||||
|   2... agent:sample01 job:00002 message:job execution result:OK | ||||
|   Job 00002 completed; result: None; | ||||
| 
 | ||||
|   >>> for r in master.logger.records: | ||||
|   ...     print r | ||||
|   2... agent:sample01 job:00001 message:job execution result:OK | ||||
|   2... agent:sample01 job:00002 message:job execution result:OK | ||||
| 
 | ||||
| 
 | ||||
| Using the Twisted-based Scheduler | ||||
| ================================= | ||||
| 
 | ||||
| By specifying the core scheduler in the agent's configuration this will be | ||||
| used automatically for scheduling. | ||||
| 
 | ||||
| In addition, we use another sample controller, now also the twisted-based | ||||
| from the core package. This will in turn set up a queueable agent from | ||||
| the core package so that now everything is running under the control of | ||||
| the twisted reactor. | ||||
| 
 | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... ''' | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
| 
 | ||||
|   >>> master.scheduler | ||||
|   <cybertools.agent.core.schedule.Scheduler object ...> | ||||
| 
 | ||||
| We enter the same job specification as above. | ||||
| 
 | ||||
|   >>> master.controllers[0].enterJob('sample', 'sample01') | ||||
| 
 | ||||
| Now the job is not executed immediately - we have to hand over control to | ||||
| the twisted reactor first. The running of the reactor is simulated by | ||||
| the ``iterate()`` method provided for testing. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
|   >>> tester.iterate() | ||||
|   Job 00001 on agent sample01 has been executed. | ||||
|   Job 00001 completed; result: Done; | ||||
							
								
								
									
										10
									
								
								agent/agent.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								agent/agent.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| # | ||||
| #   Standard configuration for agent application | ||||
| # | ||||
| #   $Id$ | ||||
| # | ||||
| 
 | ||||
| controller(names=['cmdline', 'telnet']) | ||||
| controller.telnet.port = 5001 | ||||
| scheduler(name='core') | ||||
| logger(name='default', standard=30) | ||||
							
								
								
									
										31
									
								
								agent/app.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										31
									
								
								agent/app.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent application. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.application import service | ||||
| 
 | ||||
| from cybertools.agent import main | ||||
| 
 | ||||
| 
 | ||||
| application = main.application = service.Application('Agent Application') | ||||
| main.setup() | ||||
							
								
								
									
										121
									
								
								agent/base/agent.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								agent/base/agent.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent base and sample classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.common import states | ||||
| from cybertools.agent.components import agents, controllers, jobs | ||||
| from cybertools.agent.components import loggers, schedulers | ||||
| from cybertools.agent.components import servers, clients | ||||
| from cybertools.agent.interfaces import IAgent | ||||
| from cybertools.util.config import Configurator | ||||
| 
 | ||||
| 
 | ||||
| class Agent(object): | ||||
| 
 | ||||
|     implements(IAgent) | ||||
| 
 | ||||
|     name = '???' | ||||
|     master = None | ||||
|     config = None | ||||
|     logger = None | ||||
| 
 | ||||
|     def __init__(self, master): | ||||
|         self.master = master | ||||
|         self.config = master.config | ||||
|         self.logger = master.logger | ||||
| 
 | ||||
|     def send(self, job): | ||||
|         self.execute(job) | ||||
| 
 | ||||
|     def execute(self, job): | ||||
|         pass | ||||
| 
 | ||||
|     def log(self, job, result='OK'): | ||||
|         self.logger.log(dict(message='job execution', job=job.identifier, | ||||
|                         agent=self.name, result=result)) | ||||
| 
 | ||||
| 
 | ||||
| class Master(Agent): | ||||
| 
 | ||||
|     name = 'master' | ||||
|     scheduler = None | ||||
|     logger = None | ||||
| 
 | ||||
|     def __init__(self, configuration): | ||||
|         if isinstance(configuration, Configurator): | ||||
|             self.config = configuration | ||||
|         else:   # configuration is path to config file | ||||
|             self.config = Configurator() | ||||
|             self.config.load(configuration) | ||||
|         self.master = self | ||||
|         self.controllers = [] | ||||
|         self.children = {} | ||||
|         self.servers = [] | ||||
| 
 | ||||
|     def setup(self): | ||||
|         config = self.config | ||||
|         self.logger = loggers(self, name=config.logger.name) | ||||
|         print 'Starting agent application...' | ||||
|         for n in config.controller.names: | ||||
|             self.controllers.append(controllers(self, n)) | ||||
|         self.scheduler = schedulers(self, name=config.scheduler.name) | ||||
|         for cont in self.controllers: | ||||
|             cont.setup() | ||||
|         print 'Using controllers %s.' % ', '.join(config.controller.names) | ||||
|         for n in  config.talk.server.names: | ||||
|             server = servers(self, n) | ||||
|             self.servers.append(server) | ||||
|             server.setup() | ||||
| 
 | ||||
|     def setupAgents(self, controller, agentSpecs): | ||||
|         for spec in agentSpecs: | ||||
|             agent = agents(self, spec.type) | ||||
|             agent.name = spec.name | ||||
|             self.children[spec.name] = agent | ||||
| 
 | ||||
|     def setupJobs(self, controller, jobSpecs): | ||||
|         for spec in jobSpecs: | ||||
|             job = jobs(self.scheduler, spec.type) | ||||
|             job.agent = self.children[spec.agent] | ||||
|             job.identifier = spec.identifier | ||||
|             job.controller = controller | ||||
|             job.params = spec.params | ||||
|             self.scheduler.schedule(job, spec.startTime) | ||||
| 
 | ||||
|     def notify(self, job, result=None, message=''): | ||||
|         if job.state.hasFinished(): | ||||
|             job.controller.notify(job.identifier, job.state, result, message) | ||||
| 
 | ||||
| 
 | ||||
| class SampleAgent(Agent): | ||||
| 
 | ||||
|     def execute(self, job): | ||||
|         job.state = states.running | ||||
|         print 'Job %s on agent %s has been executed.' % (job.identifier, self.name) | ||||
|         self.log(job) | ||||
|         job.state = states.completed | ||||
|         self.master.notify(job) | ||||
| 
 | ||||
| agents.register(SampleAgent, Master, name='base.sample') | ||||
							
								
								
									
										100
									
								
								agent/base/control.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								agent/base/control.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,100 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Base/sample controller implementation. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.components import controllers | ||||
| from cybertools.agent.interfaces import IController | ||||
| 
 | ||||
| 
 | ||||
| class Controller(object): | ||||
| 
 | ||||
|     implements(IController) | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
| 
 | ||||
|     def setup(self): | ||||
|         self.agent.setupAgents(self, self._getAgents()) | ||||
|         self.agent.setupJobs(self, self._getCurrentJobs()) | ||||
| 
 | ||||
|     def _getAgents(self): | ||||
|         return [] | ||||
| 
 | ||||
|     def _getCurrentJobs(self): | ||||
|         return [] | ||||
| 
 | ||||
|     def notify(self, identifier, state, result=None, message=''): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class SampleController(Controller): | ||||
| 
 | ||||
|     jobNumber = 0 | ||||
|     result = None | ||||
| 
 | ||||
|     agents = (('sample01', 'base.sample'),) | ||||
| 
 | ||||
|     def notify(self, identifier, state, result=None, message=''): | ||||
|         self.result = result | ||||
|         msg = ('Job %s %s; result: %s; %s' % | ||||
|                (identifier, state, result, message)) | ||||
|         print msg | ||||
| 
 | ||||
|     def _getAgents(self): | ||||
|         return [AgentSpecification(name, type) for name, type in self.agents] | ||||
| 
 | ||||
|     def createAgent(self, agentType, name): | ||||
|         spec = AgentSpecification(name, agentType) | ||||
|         self.agent.setupAgents(self, [spec]) | ||||
| 
 | ||||
|     def enterJob(self, jobType, agent, **kw): | ||||
|         self.jobNumber += 1 | ||||
|         spec = JobSpecification(jobType, '%05i' % self.jobNumber, agent=agent, **kw) | ||||
|         self.agent.setupJobs(self, [spec]) | ||||
| 
 | ||||
| controllers.register(SampleController, Master, name='base.sample') | ||||
| 
 | ||||
| 
 | ||||
| class AgentSpecification(object): | ||||
| 
 | ||||
|     def __init__(self, name, type, **kw): | ||||
|         self.name = name | ||||
|         self.type = type | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
| 
 | ||||
| 
 | ||||
| class JobSpecification(object): | ||||
| 
 | ||||
|     startTime = None | ||||
| 
 | ||||
|     def __init__(self, type, identifier, **kw): | ||||
|         self.type = type | ||||
|         self.identifier = identifier | ||||
|         self.params = kw.pop('params', {}) | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
| 
 | ||||
							
								
								
									
										63
									
								
								agent/base/job.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								agent/base/job.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| The real agent stuff. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.schedule import Scheduler | ||||
| from cybertools.agent.common import states | ||||
| from cybertools.agent.components import jobs | ||||
| from cybertools.agent.interfaces import IScheduledJob | ||||
| 
 | ||||
| 
 | ||||
| class Job(object): | ||||
| 
 | ||||
|     implements(IScheduledJob) | ||||
| 
 | ||||
|     identifier = '???' | ||||
|     agent = None | ||||
|     startTime = None | ||||
|     repeat = 0 | ||||
|     state = states.initialized | ||||
| 
 | ||||
|     def __init__(self, scheduler): | ||||
|         self.scheduler = scheduler | ||||
|         self.params = {} | ||||
|         self.successors = [] | ||||
| 
 | ||||
|     def execute(self): | ||||
|         if self.agent is not None: | ||||
|             self.agent.send(self) | ||||
|             self.state = states.submitted | ||||
| 
 | ||||
|     def reschedule(self, startTime=None): | ||||
|         self.scheduler.schedule(self.copy(), startTime) | ||||
| 
 | ||||
|     def copy(self): | ||||
|         newJob = self.__class__(self.scheduler) | ||||
|         newJob.agent = self.agent | ||||
|         newJob.params = self.params | ||||
|         newJob.repeat = self.repeat | ||||
|         newJob.successors = [s.copy() for s in self.successors] | ||||
| 
 | ||||
| jobs.register(Job, Scheduler, name='sample') | ||||
							
								
								
									
										81
									
								
								agent/base/log.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								agent/base/log.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Log information management. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import logging | ||||
| import sys | ||||
| import time | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Agent | ||||
| from cybertools.agent.components import loggers | ||||
| from cybertools.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(object): | ||||
| 
 | ||||
|     implements(ILogger) | ||||
| 
 | ||||
|     recordFactory = LogRecord | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
|         self.records = [] | ||||
|         self.setup() | ||||
|         self.externalLoggers = [] | ||||
| 
 | ||||
|     def setup(self): | ||||
|         conf = self.agent.config.logger | ||||
|         self.externalLoggers = [] | ||||
|         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.records.append(record) | ||||
|         for logger in self.externalLoggers: | ||||
|             logger.info(str(record)) | ||||
| 
 | ||||
| 
 | ||||
| loggers.register(Logger, Agent, name='default') | ||||
							
								
								
									
										9
									
								
								agent/base/sample.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								agent/base/sample.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| # | ||||
| #   sample.cfg - agent configuration for demonstration and testing purposes | ||||
| # | ||||
| #   $Id$ | ||||
| # | ||||
| 
 | ||||
| controller(names=['base.sample']) | ||||
| scheduler(name='sample') | ||||
| logger(name='default', standard=30) | ||||
							
								
								
									
										46
									
								
								agent/base/schedule.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								agent/base/schedule.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Basic (sample) job scheduler. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from time import time | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.common import states | ||||
| from cybertools.agent.components import schedulers | ||||
| from cybertools.agent.interfaces import IScheduler | ||||
| 
 | ||||
| 
 | ||||
| class Scheduler(object): | ||||
| 
 | ||||
|     implements(IScheduler) | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
| 
 | ||||
|     def schedule(self, job, startTime=None): | ||||
|         job.startTime = startTime or int(time()) | ||||
|         job.state = states.scheduled | ||||
|         job.execute()   # the sample scheduler does not care about startTime | ||||
| 
 | ||||
| schedulers.register(Scheduler, Master, name='sample') | ||||
							
								
								
									
										50
									
								
								agent/common.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								agent/common.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Miscellaneous common stuff. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from cybertools.util.jeep import Jeep | ||||
| 
 | ||||
| 
 | ||||
| class JobState(object): | ||||
| 
 | ||||
|     def __init__(self, name, value): | ||||
|         self.name = name | ||||
|         self.value = value | ||||
| 
 | ||||
|     def hasError(self): | ||||
|         return self.value < 0 | ||||
| 
 | ||||
|     def hasFinished(self): | ||||
|         return (self.value <= states.aborted.value or | ||||
|                 self.value >= states.completed.value) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return '<JobState %s>' % self.name | ||||
| 
 | ||||
| 
 | ||||
| states = Jeep([JobState(n, v) for n, v in | ||||
|                 (('initialized', 0), ('scheduled', 1), ('submitted', 2), | ||||
|                  ('running', 3), ('completed', 4), ('aborted', -1))]) | ||||
							
								
								
									
										34
									
								
								agent/components.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								agent/components.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Component registries. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from cybertools.util.adapter import AdapterFactory | ||||
| 
 | ||||
| 
 | ||||
| agents = AdapterFactory() | ||||
| controllers = AdapterFactory() | ||||
| jobs = AdapterFactory() | ||||
| loggers = AdapterFactory() | ||||
| schedulers = AdapterFactory() | ||||
| servers = AdapterFactory() | ||||
| clients = AdapterFactory() | ||||
							
								
								
									
										145
									
								
								agent/control/cmdline.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								agent/control/cmdline.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,145 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Base/sample controller implementation. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.application import service, internet | ||||
| from twisted.internet import protocol, reactor, stdio | ||||
| from twisted.protocols import basic | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.core.control import SampleController | ||||
| from cybertools.agent.components import controllers | ||||
| 
 | ||||
| 
 | ||||
| class CmdlineController(SampleController): | ||||
| 
 | ||||
|     def setup(self): | ||||
|         super(CmdlineController, self).setup() | ||||
|         prot = CmdlineProtocol() | ||||
|         prot.controller = self | ||||
|         stdio.StandardIO(prot) | ||||
|         self.results = {} | ||||
| 
 | ||||
| 
 | ||||
| controllers.register(CmdlineController, Master, name='cmdline') | ||||
| 
 | ||||
| 
 | ||||
| class TelnetController(CmdlineController): | ||||
| 
 | ||||
|     def setup(self): | ||||
|         super(CmdlineController, self).setup() | ||||
|         port = self.agent.config.controller.telnet.port | ||||
|         from cybertools.agent.main import application | ||||
|         if application is None: | ||||
|             reactor.listenTCP(port, TelnetServerFactory(self)) | ||||
|         else: | ||||
|             service = internet.TCPServer(port, TelnetServerFactory(self)) | ||||
|             service.setServiceParent(application) | ||||
| 
 | ||||
| controllers.register(TelnetController, Master, name='telnet') | ||||
| 
 | ||||
| 
 | ||||
| class CmdlineProtocol(basic.LineReceiver): | ||||
| 
 | ||||
|     delimiter = '\n' | ||||
|     controller = None | ||||
| 
 | ||||
|     def connectionMade(self): | ||||
|         self.sendLine("Agent console. Type 'help' for help.") | ||||
|         self.transport.write('> ') | ||||
| 
 | ||||
|     def lineReceived(self, line): | ||||
|         if not line: | ||||
|             return | ||||
|         commandParts = line.split() | ||||
|         command = commandParts[0].lower() | ||||
|         args = commandParts[1:] | ||||
|         posArgs = [] | ||||
|         kwArgs = {} | ||||
|         for arg in args: | ||||
|             if '=' in arg:  # directory='...' | ||||
|                 key, value = arg.split('=', 1) | ||||
|                 if value in ('True', 'False'): | ||||
|                     value = eval(value) | ||||
|                 #elif value.isdigit(): | ||||
|                 #    value = int(value) | ||||
|                 kwArgs[key] = value | ||||
|             else: | ||||
|                 posArgs.append(arg) | ||||
|         try: | ||||
|             method = getattr(self, 'do_' + command) | ||||
|         except AttributeError, e: | ||||
|             self.sendLine('Error: no such command.') | ||||
|         else: | ||||
|             try: | ||||
|                 method(*posArgs, **kwArgs) | ||||
|             except Exception, e: | ||||
|                 self.sendLine('Error: ' + str(e)) | ||||
|         self.transport.write('> ') | ||||
| 
 | ||||
|     def do_help(self, command=None): | ||||
|         "Help" | ||||
|         if command: | ||||
|             self.sendLine(getattr(self, 'do_' + command).__doc__) | ||||
|         else: | ||||
|             commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')] | ||||
|             self.sendLine("Valid commands: " +" ".join(commands)) | ||||
| 
 | ||||
|     def do_shutdown(self): | ||||
|         "Shut down" | ||||
|         self.sendLine('Shutting down.') | ||||
|         reactor.stop() | ||||
| 
 | ||||
|     def do_agent(self, agentType='crawl.filesystem', name='a1'): | ||||
|         "Create agent" | ||||
|         self.controller.createAgent(agentType, name) | ||||
| 
 | ||||
|     def do_job(self, jobType='sample', agent='a1', **kw): | ||||
|         "Enter job" | ||||
|         self.controller.enterJob(jobType, agent, params=kw) | ||||
| 
 | ||||
|     def do_showresult(self): | ||||
|         "Show last result" | ||||
|         print self.controller.result | ||||
| 
 | ||||
| 
 | ||||
| class TelnetProtocol(CmdlineProtocol): | ||||
| 
 | ||||
|     delimiter = '\r\n' | ||||
| 
 | ||||
|     def do_quit(self): | ||||
|         self.sendLine('Goodbye.') | ||||
|         self.transport.loseConnection() | ||||
| 
 | ||||
| 
 | ||||
| class TelnetServerFactory(protocol.ServerFactory): | ||||
| 
 | ||||
|     def __init__(self, controller): | ||||
|         self.controller = controller | ||||
| 
 | ||||
|     def protocol(self, *args, **kw): | ||||
|         prot = TelnetProtocol(*args, **kw) | ||||
|         prot.controller = self.controller | ||||
|         return prot | ||||
| 
 | ||||
							
								
								
									
										38
									
								
								agent/control/remote.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								agent/control/remote.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Controller that accepts and processes requests via XML-RPC. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.core.control import SampleController | ||||
| from cybertools.agent.components import controllers | ||||
| 
 | ||||
| 
 | ||||
| class RemoteController(SampleController): | ||||
| 
 | ||||
|     def setup(self): | ||||
|         super(RemoteController, self).setup() | ||||
| 
 | ||||
| 
 | ||||
| controllers.register(RemoteController, Master, name='remote') | ||||
							
								
								
									
										87
									
								
								agent/core/agent.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								agent/core/agent.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Queueable agent base/sample classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.internet.defer import succeed | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Agent, Master | ||||
| from cybertools.agent.common import states | ||||
| from cybertools.agent.components import agents | ||||
| from cybertools.agent.interfaces import IQueueableAgent | ||||
| 
 | ||||
| 
 | ||||
| class QueueableAgent(Agent): | ||||
| 
 | ||||
|     implements(IQueueableAgent) | ||||
| 
 | ||||
|     currentJob = None | ||||
| 
 | ||||
|     def __init__(self, master): | ||||
|         super(QueueableAgent, self).__init__(master) | ||||
|         self.queue = [] | ||||
| 
 | ||||
|     def send(self, job): | ||||
|         if self.currentJob is None: | ||||
|             if self.queue:  # this should not happen... | ||||
|                 self.queue.insert(0, job) | ||||
|                 job = self.queue.pop() | ||||
|             self.execute(job) | ||||
|         else: | ||||
|             self.queue.insert(0, job) | ||||
| 
 | ||||
|     def execute(self, job): | ||||
|         job.state = states.running | ||||
|         self.currentJob = job | ||||
|         self.params = job.params | ||||
|         d = self.process() | ||||
|         d.addCallbacks(self.completed, self.error) | ||||
| 
 | ||||
|     def process(self): | ||||
|         # do something with the current job, return a deferred | ||||
|         print ('Job %s on agent %s has been executed.' | ||||
|             % (self.currentJob.identifier, self.name)) | ||||
|         return succeed('Done') | ||||
| 
 | ||||
|     def completed(self, result): | ||||
|         job = self.currentJob | ||||
|         job.state = states.completed | ||||
|         self.log(job) | ||||
|         self.master.notify(job, result) | ||||
|         self.finishJob() | ||||
| 
 | ||||
|     def error(self, result): | ||||
|         print '*** error', result | ||||
|         job = self.currentJob | ||||
|         job.state = states.aborted | ||||
|         self.log(self.currentJob, result='Error') | ||||
|         self.master.notify(job, result) | ||||
|         self.finishJob() | ||||
| 
 | ||||
|     def finishJob(self): | ||||
|         self.currentJob = None | ||||
|         if self.queue: | ||||
|             job = self.queue.pop() | ||||
|             self.execute(job, job.params) | ||||
| 
 | ||||
| agents.register(QueueableAgent, Master, name='core.sample') | ||||
							
								
								
									
										36
									
								
								agent/core/control.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								agent/core/control.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Base/sample controller implementation. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.base.control import SampleController, AgentSpecification | ||||
| from cybertools.agent.components import controllers | ||||
| 
 | ||||
| 
 | ||||
| class SampleController(SampleController): | ||||
| 
 | ||||
|     agents = (('sample01', 'core.sample'),) | ||||
| 
 | ||||
| controllers.register(SampleController, Master, name='core.sample') | ||||
							
								
								
									
										57
									
								
								agent/core/schedule.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								agent/core/schedule.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Basic job scheduler using twisted. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from time import time | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.base.schedule import Scheduler as BaseScheduler | ||||
| from cybertools.agent.common import states | ||||
| from cybertools.agent.components import schedulers | ||||
| from cybertools.agent.interfaces import IScheduler | ||||
| 
 | ||||
| from twisted.internet import reactor | ||||
| from twisted.internet.defer import Deferred | ||||
| 
 | ||||
| 
 | ||||
| class Scheduler(BaseScheduler): | ||||
| 
 | ||||
|     implements(IScheduler) | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
| 
 | ||||
|     def schedule(self, job, startTime=None): | ||||
|         job.startTime = startTime or int(time()) | ||||
|         if startTime is None: | ||||
|             startTime = int(time()) | ||||
|         job.startTime = startTime | ||||
|         job.state = states.scheduled | ||||
|         reactor.callLater(startTime-int(time()), job.execute) | ||||
|         return startTime | ||||
| 
 | ||||
|     def getJobsToExecute(startTime=0): | ||||
|         return [j for j in self.queue.values() if startTime <= j.startTime] | ||||
| 
 | ||||
| schedulers.register(Scheduler, Master, name='core') | ||||
							
								
								
									
										46
									
								
								agent/crawl/README.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								agent/crawl/README.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... ''' | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
| 
 | ||||
| 
 | ||||
| Crawler | ||||
| ======= | ||||
| 
 | ||||
| The agent uses Twisted's cooperative multitasking model. | ||||
| 
 | ||||
| Crawler is the base class for all derived crawlers like the filesystem crawler | ||||
| and the mailcrawler. The SampleCrawler returns a deferred that already had a | ||||
| callback being called, so it will return at once. | ||||
| 
 | ||||
| Returns a deferred that must be supplied with a callback method (and in | ||||
| most cases also an errback method). | ||||
| 
 | ||||
| We create the sample crawler via the master's controller. The sample | ||||
| controller provides a simple method for this purpose. | ||||
| 
 | ||||
|   >>> controller = master.controllers[0] | ||||
|   >>> controller.createAgent('crawl.sample', 'crawler01') | ||||
| 
 | ||||
| In the next step we request the start of a job, again via the controller. | ||||
| 
 | ||||
|   >>> controller.enterJob('sample', 'crawler01') | ||||
| 
 | ||||
| The job is not executed immediately - we have to hand over control to | ||||
| the twisted reactor first. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
|   >>> tester.iterate() | ||||
|   SampleCrawler is collecting. | ||||
|   Job 00001 completed; result: []; | ||||
							
								
								
									
										92
									
								
								agent/crawl/base.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								agent/crawl/base.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Crawl base and sample classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.core.agent import QueueableAgent | ||||
| from cybertools.agent.interfaces import ICrawler | ||||
| from cybertools.agent.interfaces import IResource, IMetadataSet | ||||
| from cybertools.agent.components import agents | ||||
| from twisted.internet.defer import succeed | ||||
| 
 | ||||
| 
 | ||||
| class Crawler(QueueableAgent): | ||||
| 
 | ||||
|     implements(ICrawler) | ||||
| 
 | ||||
|     def __init__(self, master, params={}): | ||||
|         super(Crawler, self).__init__(master) | ||||
| 
 | ||||
|     def process(self): | ||||
|         return self.collect() | ||||
| 
 | ||||
|     def collect(self, filter=None): | ||||
|         d = defer.succeed([]) | ||||
|         return d | ||||
| 
 | ||||
| 
 | ||||
| class SampleCrawler(Crawler): | ||||
| 
 | ||||
|     def collect(self, filter=None): | ||||
|         print 'SampleCrawler is collecting.' | ||||
|         d = succeed([]) | ||||
|         return d | ||||
| 
 | ||||
| agents.register(SampleCrawler, Master, name='crawl.sample') | ||||
| 
 | ||||
| 
 | ||||
| class Resource(object): | ||||
| 
 | ||||
|     implements(IResource) | ||||
| 
 | ||||
|     data = file = path = None | ||||
|     type = 'sample' | ||||
|     contentType = 'text/plain' | ||||
|     encoding = '' | ||||
|     application = 'sample' | ||||
|     metadata = None | ||||
| 
 | ||||
|     def __init__(self, data=None, **kw): | ||||
|         if data is not None: | ||||
|             self.data = data | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
|         self.subResources = [] | ||||
| 
 | ||||
| 
 | ||||
| class Metadata(dict): | ||||
| 
 | ||||
|     implements(IMetadataSet) | ||||
| 
 | ||||
|     def __init__(self, data=dict()): | ||||
|         self.update(data) | ||||
| 
 | ||||
|     def asXML(self): | ||||
|         # TODO... | ||||
|         return '' | ||||
| 
 | ||||
|     def set(self, key, value): | ||||
|         self['key'] = value | ||||
| 
 | ||||
							
								
								
									
										85
									
								
								agent/crawl/filesystem.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								agent/crawl/filesystem.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Filesystem crawler. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| from fnmatch import filter | ||||
| from datetime import datetime | ||||
| from twisted.internet.defer import Deferred | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.components import agents | ||||
| from cybertools.agent.crawl.base import Resource, Metadata | ||||
| from cybertools.agent.crawl.base import Crawler | ||||
| from cybertools.agent.util.task import coiterate | ||||
| 
 | ||||
| 
 | ||||
| class FilesystemCrawler(Crawler): | ||||
| 
 | ||||
|     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')] | ||||
|             for x in self.loadFiles(path, files, pattern, lastRun): | ||||
|                 yield None | ||||
| 
 | ||||
|     def loadFiles(self, path, files, pattern, lastRun): | ||||
|         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(path=filename, metadata=Metadata(meta))) | ||||
|             yield None | ||||
| 
 | ||||
| agents.register(FilesystemCrawler, Master, name='crawl.filesystem') | ||||
| 
 | ||||
| 
 | ||||
| class FileResource(Resource): | ||||
| 
 | ||||
|     type = 'file' | ||||
|     application = 'filesystem' | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|         f = open(self.path, 'r') | ||||
|         text = f.read() | ||||
|         f.close() | ||||
|         return text | ||||
							
								
								
									
										42
									
								
								agent/crawl/filesystem.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								agent/crawl/filesystem.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
|   >>> import os | ||||
|   >>> from time import time | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester, baseDir | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... ''' | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
| 
 | ||||
| 
 | ||||
| Filesystem Crawler | ||||
| ================== | ||||
| 
 | ||||
|   >>> controller = master.controllers[0] | ||||
|   >>> controller.createAgent('crawl.filesystem', 'sample03') | ||||
| 
 | ||||
| In the next step we request the start of a job, again via the controller. | ||||
| 
 | ||||
|   >>> path = os.path.join(baseDir, 'testing', 'data') | ||||
|   >>> controller.enterJob('sample', 'sample03', params=dict(directory=path)) | ||||
| 
 | ||||
| The job is not executed immediately - we have to hand over control to | ||||
| the twisted reactor first. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
|   >>> tester.iterate() | ||||
|   Job 00001 completed; result: [..., ...]; | ||||
| 
 | ||||
|   >>> r0 = controller.result[0] | ||||
|   >>> r0.metadata, r0.data | ||||
|   ({'path': '...file1.txt'}, 'Data from file1.txt') | ||||
							
								
								
									
										76
									
								
								agent/crawl/mail.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								agent/crawl/mail.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Crawl base and sample classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Agent, Master | ||||
| from cybertools.agent.crawl.base import Resource | ||||
| from cybertools.agent.crawl.base import Metadata | ||||
| from cybertools.agent.crawl.base import Crawler | ||||
| from cybertools.agent.components import agents | ||||
| from twisted.internet.defer import succeed | ||||
| 
 | ||||
| 
 | ||||
| class MailCrawler(Crawler): | ||||
| 
 | ||||
|     def __init__(self, master): | ||||
|         super(MailCrawler, self).__init__(master) | ||||
|         self.result = [] | ||||
| 
 | ||||
|     def collect(self, filter=None): | ||||
|         print 'MailCrawler is collecting.' | ||||
|         d = self.crawlFolders() | ||||
|         return d | ||||
| 
 | ||||
|     def fetchCriteria(self): | ||||
|         pass | ||||
| 
 | ||||
|     def crawlFolders(self): | ||||
|         return succeed([]) | ||||
| 
 | ||||
|     def loadMailsFromFolder(self, folder): | ||||
|         pass | ||||
| 
 | ||||
|     def createResource(self, data, path=None, application=None, metadata=None): | ||||
|         resource = MailResource(data=data, path=path, application=application, | ||||
|                                 metadata=metadata) | ||||
|         self.result.append(resource) | ||||
|          | ||||
|     def createMetadata(self, metadata): | ||||
|         metadata = Metadata(metadata) | ||||
| ##        for k, v in kw.items(): | ||||
| ##            metadata[k] = v | ||||
|         return metadata | ||||
| 
 | ||||
|     def login(self): | ||||
|         pass | ||||
| 
 | ||||
| agents.register(MailCrawler, Master, name='crawl.mail') | ||||
| 
 | ||||
| 
 | ||||
| class MailResource(Resource): | ||||
| 
 | ||||
|     type = 'email' | ||||
|     application = 'mailclient' | ||||
| 
 | ||||
							
								
								
									
										231
									
								
								agent/crawl/outlook.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								agent/crawl/outlook.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,231 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Outlook Crawler Class. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import re | ||||
| from email import MIMEMultipart | ||||
| import tempfile | ||||
| import os | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| from twisted.internet import defer | ||||
| #from pywintypes import com_error | ||||
| #The watsup import is needed as soon as we start handling the Outlook Pop-Up | ||||
| #again | ||||
| #This should also be integrated within the wrapper-api for doctests | ||||
| #from watsup.winGuiAuto import findTopWindow, findControl, findControls, clickButton, \ | ||||
| #                              getComboboxItems, selectComboboxItem, setCheckBox | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Agent, Master | ||||
| from cybertools.agent.crawl.mail import MailCrawler | ||||
| from cybertools.agent.crawl.mail import MailResource | ||||
| from cybertools.agent.crawl.filesystem import FileResource | ||||
| from cybertools.agent.components import agents | ||||
| from cybertools.agent.system.windows import api | ||||
| from cybertools.agent.util.task import coiterate | ||||
| from cybertools.agent.system.windows.codepages import codepages | ||||
| 
 | ||||
| # some constants | ||||
| COMMASPACE = ', ' | ||||
| 
 | ||||
| class OutlookCrawler(MailCrawler): | ||||
| 
 | ||||
|     keys = "" | ||||
|     inbox = "" | ||||
|     subfolders = "" | ||||
|     pattern = "" | ||||
| 
 | ||||
|     def collect(self, filter=None): | ||||
|         self.result = [] | ||||
|         self.d = defer.Deferred() | ||||
|         self.oOutlookApp = None | ||||
|         if self.findOutlook(): | ||||
|             self.fetchCriteria() | ||||
|             coiterate(self.crawlFolders()).addCallback(self.finished).addErrback(self.error) | ||||
|         else: | ||||
|             pass | ||||
|             #self.d.addErrback([]) | ||||
|         return self.d | ||||
| 
 | ||||
|     def error(self, reason): | ||||
|         print '***** error', | ||||
|         print reason | ||||
| 
 | ||||
|     def finished(self, result): | ||||
|         self.d.callback(self.result) | ||||
| 
 | ||||
|     def fetchCriteria(self): | ||||
|         criteria = self.params | ||||
|         self.keys = criteria.get('keys') | ||||
|         self.inbox = criteria.get('inbox') #boolean | ||||
|         self.subfolders = criteria.get('subfolders') #boolean | ||||
|         self.pattern = criteria.get('pattern') | ||||
|         if self.pattern != '' and self.pattern != None: | ||||
|             self.pattern = re.compile(criteria.get('pattern') or '.*') | ||||
| 
 | ||||
|     def crawlFolders(self): | ||||
|         onMAPI = self.oOutlookApp.GetNamespace("MAPI") | ||||
|         ofInbox = \ | ||||
|             onMAPI.GetDefaultFolder(api.client.constants.olFolderInbox) | ||||
|         # fetch mails from inbox | ||||
|         if self.inbox: | ||||
|             for m in self.loadMailsFromFolder(ofInbox): | ||||
|                 yield None | ||||
|         # fetch mails of inbox subfolders | ||||
|         if self.subfolders and self.pattern is None: | ||||
|             lInboxSubfolders = getattr(ofInbox, 'Folders') | ||||
|             for of in range(lInboxSubfolders.__len__()): | ||||
|                 # get a MAPI-subfolder object and load its emails | ||||
|                 for m in self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)): | ||||
|                     yield None | ||||
|         elif self.subfolders and self.pattern: | ||||
|             lInboxSubfolders = getattr(ofInbox, 'Folders') | ||||
|             for of in range(lInboxSubfolders.__len__()): | ||||
|                 # get specified MAPI-subfolder object and load its emails | ||||
|                 if self.pattern.match(getattr(lInboxSubfolders.Item(of + 1), 'Name')): | ||||
|                     for m in self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)): | ||||
|                         yield None | ||||
| 
 | ||||
|     def loadMailsFromFolder(self, folder): | ||||
|         # get items of the folder | ||||
|         folderItems = getattr(folder, 'Items') | ||||
|         for item in range(len(folderItems)): | ||||
|             mail = folderItems.Item(item+1) | ||||
|             if mail.Class == api.client.constants.olMail: | ||||
|                 if self.keys is None: | ||||
|                     self.keys = [] | ||||
|                     for key in mail._prop_map_get_.items(): | ||||
|                         try: | ||||
|                             if isinstance(key[0], (int, str, unicode, bool)): | ||||
|                                 self.keys.append(key[0]) | ||||
|                         except api.com_error: | ||||
|                             pass | ||||
|                 record = {} | ||||
|                 for key in self.keys: | ||||
|                     try: | ||||
|                         if (hasattr(mail, key)): | ||||
|                             value = getattr(mail, key) | ||||
|                             if isinstance(value, (int, str, unicode, bool)): | ||||
|                                 record[key] = value | ||||
|                             else: | ||||
|                                 record[key] = None | ||||
|                     except: | ||||
|                         pass | ||||
|                 metadata = self.assembleMetadata(folder, record) | ||||
|                 # Create a resource and append it to the result list | ||||
|                 self.createResource(mail, folder, metadata) | ||||
|                 yield None | ||||
| 
 | ||||
|     def findOutlook(self): | ||||
|         outlookFound = False | ||||
|         try: | ||||
|             self.oOutlookApp = \ | ||||
|                 api.client.gencache.EnsureDispatch("Outlook.Application") | ||||
|             outlookFound = True | ||||
|         except com_error: | ||||
|             pass | ||||
|         return outlookFound | ||||
| 
 | ||||
|     def assembleMetadata(self, folder, mailAttr): | ||||
|         meta = {} | ||||
|         for key in mailAttr.keys(): | ||||
|             if isinstance(mailAttr[key], (str, unicode))\ | ||||
|                and mailAttr[key] != 'Body' and mailAttr[key] != 'HTMLBody': | ||||
|                 meta[key] = mailAttr[key].encode('utf-8') | ||||
|             elif isinstance(mailAttr[key], (list, tuple, dict)): | ||||
|                 lst = [] | ||||
|                 for rec in mailAttr[key]: | ||||
|                     lst.append(rec) | ||||
|                     meta[key] = COMMASPACE.join(lst) | ||||
|             else: | ||||
|                 meta[key] = mailAttr[key] | ||||
|         meta["path"] = folder | ||||
|         metadata = self.createMetadata(meta) | ||||
|         return metadata | ||||
|      | ||||
|     def createResource(self, mail, folder, metadata): | ||||
|         enc = None | ||||
|         textType = "application/octet-stream" | ||||
|         attachments = [] | ||||
|         mailContent = "" | ||||
|         ident = None | ||||
|         if (hasattr(mail, 'BodyFormat')): | ||||
|             value = getattr(mail, 'BodyFormat') | ||||
|             if value == 1: | ||||
|                 #1: it is a plain text mail, that is maybe decorated with | ||||
|                 #some html Tags by Outlook for formatting | ||||
|                 #so save it as plain text mail | ||||
|                 if hasattr(mail, 'Body'): | ||||
|                     mailContent = getattr(mail, 'Body') | ||||
|                     textType = "text/plain" | ||||
|                 else: | ||||
|                     mailContent = "" | ||||
|                     textType = "text/plain" | ||||
|             elif value == 2: | ||||
|                 #2: it is a HTML mail | ||||
|                 if hasattr(mail, 'HTMLBody'): | ||||
|                     mailContent = getattr(mail, 'HTMLBody') | ||||
|                     textType = "text/html" | ||||
|                 else: | ||||
|                     mailContent = "" | ||||
|                     textType = "text/html" | ||||
|         else: | ||||
|             #Could not determine BodyFormat. Try to retrieve plain text | ||||
|             if hasattr(mail, 'Body'): | ||||
|                 mailContent = getattr(mail, 'Body') | ||||
|             else: | ||||
|                 mailContent = "" | ||||
|         if hasattr(mail, 'InternetCodepage'): | ||||
|             Codepage = getattr(mail, 'InternetCodepage') | ||||
|             if codepages.has_key(Codepage): | ||||
|                 enc = codepages[Codepage] | ||||
|         if hasattr(mail, 'EntryID'): | ||||
|             ident = getattr(mail, 'EntryID') | ||||
|         if hasattr(mail, 'Attachments'): | ||||
|             attachedElems = getattr(mail, 'Attachments') | ||||
|             for item in range(1, len(attachedElems)+1): | ||||
|                 fileHandle, filePath = tempfile.mkstemp(prefix="outlook") | ||||
|                 attachedItem = attachedElems.Item(item) | ||||
|                 attachedItem.SaveAsFile(filePath) | ||||
|                 os.close(fileHandle) | ||||
|                 metadat = self.createMetadata(dict(filename=filePath)) | ||||
|                 fileRes = FileResource(data=None, | ||||
|                                        path=filePath, | ||||
|                                        metadata=metadat) | ||||
|                 attachments.append(fileRes) | ||||
|         fileHandle, filePath = tempfile.mkstemp(prefix="olmail") | ||||
|         filePointer = os.fdopen(fileHandle, "w") | ||||
|         mailContent = mailContent.encode('utf-8') | ||||
|         filePointer.write(mailContent) | ||||
|         filePointer.close() | ||||
|         resource = MailResource(data=mailContent, | ||||
|                                 contentType=textType, | ||||
|                                 encoding=enc, | ||||
|                                 path=filePath, | ||||
|                                 application='outlook', | ||||
|                                 identifier=ident, | ||||
|                                 metadata=metadata, | ||||
|                                 subResources=attachments) | ||||
|         self.result.append(resource) | ||||
| 
 | ||||
| agents.register(OutlookCrawler, Master, name='crawl.outlook') | ||||
							
								
								
									
										53
									
								
								agent/crawl/outlook.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								agent/crawl/outlook.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... system.winapi = 'testing' | ||||
|   ... ''' | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
| 
 | ||||
| 
 | ||||
| OutlookCrawler | ||||
| ============== | ||||
| 
 | ||||
| The agent uses Twisted's cooperative multitasking model. | ||||
| 
 | ||||
| OutlookCrawler is derived from MailCrawler. The OutlookCrawler returns a deferred | ||||
| which itself holds a list of MailResource Objects. | ||||
| 
 | ||||
| Returns a deferred that must be supplied with a callback method (and in | ||||
| most cases also an errback method). | ||||
| 
 | ||||
| The TestCase here is using subsidiary methods which replace calls to the "real Outlook | ||||
| dlls". | ||||
| 
 | ||||
|   >>> controller = master.controllers[0] | ||||
|   >>> controller.createAgent('crawl.outlook', 'sample02') | ||||
| 
 | ||||
| In the next step we request the start of a job, again via the controller. | ||||
| 
 | ||||
|   >>> controller.enterJob('sample', 'sample02', params=dict(inbox=True)) | ||||
| 
 | ||||
| The job is not executed immediately - we have to hand over control to | ||||
| the twisted reactor first. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
|   >>> tester.iterate() | ||||
|   Outlook.Application retrieved | ||||
|   Namespace MAPI retrieved | ||||
|   retrieving Outlook default folder | ||||
|   collecting Mails from folder | ||||
|   Attachment: Invitation.pdf | ||||
|   Attachment: 21.pdf | ||||
|   Attachment saved | ||||
|   Attachment saved | ||||
|   Job 00001 completed; result: [<...MailResource...>, <...MailResource...>, <...MailResource...>]; | ||||
							
								
								
									
										278
									
								
								agent/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								agent/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,278 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| cybertools agent interfaces. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import Interface, Attribute | ||||
| 
 | ||||
| from cybertools.util.jeep import Jeep | ||||
| 
 | ||||
| 
 | ||||
| # agents | ||||
| 
 | ||||
| class IAgent(Interface): | ||||
|     """ An agent waits for jobs to execute. | ||||
|     """ | ||||
| 
 | ||||
|     name = Attribute('A name identifying the agent.') | ||||
|     master = Attribute('IMaster instance.') | ||||
|     config = Attribute('Configuration settings.') | ||||
|     logger = Attribute('Logger instance to be used for recording ' | ||||
|                     'job execution and execution results.') | ||||
| 
 | ||||
|     def send(job): | ||||
|         """ If the agent supports queueing and the agent is busy put | ||||
|             job in queue, otherwise just execute the job. | ||||
|         """ | ||||
| 
 | ||||
|     def execute(job): | ||||
|         """ Execute a job using the parameters given. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class IQueueableAgent(Interface): | ||||
|     """ An agent that keeps a queue of jobs. A queueable agent | ||||
|         executes not more than one job at a time; when a running | ||||
|         job is finished the next one will be taken from the queue. | ||||
|     """ | ||||
| 
 | ||||
|     queue = Attribute('A sequence of jobs to execute.') | ||||
| 
 | ||||
|     def process(): | ||||
|         """ Do the real work asynchronously, returning a deferred. | ||||
|             This method will be called by execute(). | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class IMaster(IAgent): | ||||
|     """ The top-level controller agent. | ||||
|     """ | ||||
| 
 | ||||
|     config = Attribute('Central configuration settings.') | ||||
|     controllers = Attribute('Collection of IController instances.') | ||||
|     scheduler = Attribute('IScheduler instance.') | ||||
|     children = Attribute('A collection of agents that are managed by this ' | ||||
|                     'master.') | ||||
| 
 | ||||
|     def setup(): | ||||
|         """ Set up the master agent by triggering all assigned controllers. | ||||
|             Each controller will then call the master agent's callback | ||||
|             methods ``setupAgents()`` and ``setupJobs()``. | ||||
|         """ | ||||
| 
 | ||||
|     def setupAgents(controller, agentSpecs): | ||||
|         """ Callback for loading agent specifications from the controller | ||||
|             and setting up the corresponding agents. | ||||
| 
 | ||||
|             Will be called upon agent setup and later when the controller | ||||
|             wants to provide new agent information. | ||||
|         """ | ||||
| 
 | ||||
|     def setupJobs(controller, jobSpecs): | ||||
|         """ Callback for loading the specifications of active jobs from | ||||
|             the controller and scheduling the corresponding jobs. | ||||
| 
 | ||||
|             Will be called upon agent setup and later when the controller | ||||
|             wants to provide new job information. | ||||
|         """ | ||||
| 
 | ||||
|     def notify(job, result=None, message=''): | ||||
|         """ Callback for informing the master about the state of a job. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class ICrawler(IAgent): | ||||
|     """ Collects resources. | ||||
|     """ | ||||
| 
 | ||||
|     def collect(filter=None): | ||||
|         """ 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 ITransporter(IAgent): | ||||
|     """ Transfers one or more collected resources and corresponding | ||||
|         metadata to another entity - a remote agent or another application. | ||||
|     """ | ||||
| 
 | ||||
|     serverURL = Attribute('URL of the server the resources will be ' | ||||
|                           'transferred to. The URL also determines the ' | ||||
|                           'transfer protocol, e.g. HTTP or SFTP.') | ||||
|     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 transfer(resource): | ||||
|         """ Transfer the resource (an object providing IResource) | ||||
|             to the server and return a Deferred. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| # job control | ||||
| 
 | ||||
| class IController(Interface): | ||||
|     """ Fetches agent and job specifications from a control | ||||
|         storage and updates the storage with the status and result | ||||
|         information. | ||||
|     """ | ||||
| 
 | ||||
|     def setup(): | ||||
|         """ Set up the controller; e.g. create agents and jobs by calling | ||||
|             the agent's callback methods. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class IScheduler(Interface): | ||||
|     """ Manages jobs and cares that they are started at the appropriate | ||||
|         time by the agents responsible for it. | ||||
|     """ | ||||
| 
 | ||||
|     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 schedule the job for immediate | ||||
|             start. Return the start time with which the job has been | ||||
|             scheduled - this may be different from the start time | ||||
|             supplied. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| # jobs | ||||
| 
 | ||||
| class IScheduledJob(Interface): | ||||
|     """ A job that will be executed on some external triggering at | ||||
|         a predefined date and time - this is the basic job interface. | ||||
|     """ | ||||
| 
 | ||||
|     identifier = Attribute('A name/ID unique within the realm of the ' | ||||
|                        'controller.') | ||||
|     scheduler = Attribute('Scheduler that controls this job.') | ||||
|     agent = Attribute('Agent responsible for executing the job.') | ||||
|     controller = Attribute('Controller that issued the job.') | ||||
|     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.') | ||||
|     state = Attribute('An object representing the current state of the job.') | ||||
|     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.') | ||||
| 
 | ||||
|     def execute(): | ||||
|         """ Execute the job, typically by calling the ``execute()`` method | ||||
|             of the agent responsible for it. | ||||
|         """ | ||||
| 
 | ||||
|     def reschedule(startTime): | ||||
|         """ Re-schedule the job, setting the date/time the job should be | ||||
|             executed again. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| # information objects | ||||
| 
 | ||||
| class IResource(Interface): | ||||
|     """ Represents a data object that is collected by a crawler and | ||||
|         will be transferred to the server. | ||||
|     """ | ||||
| 
 | ||||
|     data = Attribute('A string representation of the ' | ||||
|                     'resource\'s content; may be None if the receiver of ' | ||||
|                     'the information can retrieve the data from the file or path ' | ||||
|                     'attribute.') | ||||
|     file = Attribute('A file-like object providing the data via its read() ' | ||||
|                     'method; may be None if the data or path attribute ' | ||||
|                     'is given.') | ||||
|     path = Attribute('A filesystem path for accessing the resource; may be ' | ||||
|                     'None if the data or file attribute is given.') | ||||
|     identifier = Attribute('A string (usually derived from the path) that ' | ||||
|                     'uniquely identifies the resource.') | ||||
|     type = Attribute('A string denoting the type of the resource, e.g. ' | ||||
|                     '"file" or "email".') | ||||
|     contentType = Attribute('A string denoting the MIME type of the data, ' | ||||
|                     'e.g. "text/plain" or "application/octet-stream"') | ||||
|     encoding = Attribute('Optional: a string denoting the encoding of the ' | ||||
|                     'file data, e.g. "UTF-8".') | ||||
|     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.') | ||||
|     subResources = Attribute('A collection of resources that are inherently ' | ||||
|                     'connected to or parts of this resource, e.g. attachments ' | ||||
|                     'of an email. Will be None or empty in most cases.') | ||||
| 
 | ||||
| 
 | ||||
| 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) these 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). | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| # logging | ||||
| 
 | ||||
| class ILogger(Interface): | ||||
|     """ Ordered collection (list) of log records, probably stored on some | ||||
|         external device. | ||||
|     """ | ||||
| 
 | ||||
|     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 to a | ||||
|             log file. | ||||
|         """ | ||||
							
								
								
									
										77
									
								
								agent/main.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										77
									
								
								agent/main.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| #! /usr/bin/env python2.4 | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent application. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| from twisted.internet import reactor | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| 
 | ||||
| 
 | ||||
| application = None  # contains application object if started via twistd | ||||
| 
 | ||||
| 
 | ||||
| def getConfig(): | ||||
|     agentHome = os.path.abspath(os.path.dirname(__file__)) | ||||
|     configName = 'agent.cfg' | ||||
|     configFile = open(os.path.join(agentHome, configName)) | ||||
|     config = configFile.read() | ||||
|     configFile.close() | ||||
|     return config | ||||
| 
 | ||||
| 
 | ||||
| def setup(configInfo=None): | ||||
|     if configInfo is None: | ||||
|         configInfo = getConfig() | ||||
|     master = Master(configInfo) | ||||
|     setupEnvironment(master.config) | ||||
|     master.setup() | ||||
|     return master | ||||
| 
 | ||||
| 
 | ||||
| def setupEnvironment(config): | ||||
|     # API registration: | ||||
|     from cybertools.agent.system.windows import api | ||||
|     api.setup(config) | ||||
|     from cybertools.agent.system import http, xmlrpc, sftp | ||||
|     http.setup(config) | ||||
|     xmlrpc.setup(config) | ||||
|     sftp.setup(config) | ||||
|     # self registration of components: | ||||
|     from cybertools.agent.base import agent, control, job, log, schedule | ||||
|     from cybertools.agent.core import agent, control, schedule | ||||
|     from cybertools.agent.control import cmdline, remote | ||||
|     from cybertools.agent.crawl import base, filesystem, outlook | ||||
|     from cybertools.agent.talk import http | ||||
|     from cybertools.agent.transport import remote, loops | ||||
| 
 | ||||
| 
 | ||||
| def startReactor(): | ||||
|     reactor.run() | ||||
|     print 'Agent application has been stopped.' | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     setup() | ||||
|     startReactor() | ||||
							
								
								
									
										31
									
								
								agent/system/http.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								agent/system/http.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Configuration-controlled import of HTTP communication functionality. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| def setup(config): | ||||
|     global listener, getPage | ||||
|     if config.talk.http.handler == 'testing': | ||||
|         from cybertools.agent.testing.http import listener, getPage | ||||
|     else: | ||||
|         from twisted.internet import reactor as listener | ||||
|         from twisted.web.client import getPage | ||||
							
								
								
									
										30
									
								
								agent/system/sftp.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								agent/system/sftp.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Configuration-controlled import of sftp functionality. | ||||
| 
 | ||||
| $Id: rpcapi.py | ||||
| """ | ||||
| 
 | ||||
| def setup(config): | ||||
|     global FileTransfer | ||||
|     if config.transport.remote.sftp == 'testing': | ||||
|         from cybertools.agent.testing.sftp import FileTransfer | ||||
|     else: | ||||
|         from cybertools.agent.transport.file.sftp import FileTransfer | ||||
							
								
								
									
										39
									
								
								agent/system/windows/api.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								agent/system/windows/api.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Conficuration-controlled import of Windows API functions. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| def setup(config): | ||||
|     global client, ctypes, win32api, win32process, win32con, com_error | ||||
|     if config.system.winapi == 'testing': | ||||
|         from cybertools.agent.testing.winapi import \ | ||||
|                         client, ctypes, win32api, win32process, win32con, com_error | ||||
|     else: | ||||
|         try: | ||||
|             from win32com import client | ||||
|             import ctypes | ||||
|             import win32api, win32process, win32con | ||||
|             from pywintypes import com_error | ||||
|         except ImportError: | ||||
|             from cybertools.agent.testing.winapi import \ | ||||
|                         client, ctypes, win32api, win32process, win32con, com_error | ||||
| 
 | ||||
							
								
								
									
										59
									
								
								agent/system/windows/codepages.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								agent/system/windows/codepages.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Codepages Module | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| codepages = {28596: 'iso-8859-6',\ | ||||
|              1256: 'windows-1256',\ | ||||
|              28594: 'iso-8859-4',\ | ||||
|              1257: 'windows-1257',\ | ||||
|              28592: 'iso-8859-2',\ | ||||
|              1250: 'windows-1250',\ | ||||
|              936: 'gb2312',\ | ||||
|              52936: 'hz-gb-2312',\ | ||||
|              950: 'big5',\ | ||||
|              28595: 'iso-8859-5',\ | ||||
|              20866: 'koi8-r',\ | ||||
|              21866: 'koi8-u',\ | ||||
|              1251: 'windows-1251',\ | ||||
|              28597: 'iso-8859-7',\ | ||||
|              1253: 'windows-1253',\ | ||||
|              38598: 'iso-8859-8-i',\ | ||||
|              1255: 'windows-1255',\ | ||||
|              51932: 'euc-jp',\ | ||||
|              50220: 'iso-2022-jp',\ | ||||
|              50221: 'csISO2022JP',\ | ||||
|              932: 'iso-2022-jp',\ | ||||
|              949: 'ks_c_5601-1987',\ | ||||
|              51949: 'euc-kr',\ | ||||
|              28593: 'iso-8859-3',\ | ||||
|              28605: 'iso-8859-15',\ | ||||
|              874: 'windows-874',\ | ||||
|              28599: 'iso-8859-9',\ | ||||
|              1254: 'windows-1254',\ | ||||
|              65000: 'utf-7',\ | ||||
|              65001: 'utf-8',\ | ||||
|              20127: 'us-ascii',\ | ||||
|              1258:  'windows-1258',\ | ||||
|              28591: 'iso-8859-1',\ | ||||
|              1252: 'Windows-1252' | ||||
|              } | ||||
|               | ||||
							
								
								
									
										56
									
								
								agent/system/windows/outlookdialog.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								agent/system/windows/outlookdialog.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Module for handling Outlook dialoges | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| def login(self): | ||||
|     pass | ||||
| 
 | ||||
| def handleOutlookDialog(self): | ||||
|     """ | ||||
|     This function handles the outlook dialog, which appears if someone | ||||
|     tries to access to MS Outlook. | ||||
|     """ | ||||
|     hwnd = None | ||||
|     while True: | ||||
|         hwnd = api.ctypes.windll.user32.FindWindowExA(None, hwnd, None, None) | ||||
|         if hwnd == None: | ||||
|                 break | ||||
|         else: | ||||
|             val = u"\0" * 1024 | ||||
|             api.ctypes.windll.user32.GetWindowTextW(hwnd, val, len(val)) | ||||
|             val = val.replace(u"\000", u"") | ||||
|             if val and repr(val) == "u'Microsoft Office Outlook'": | ||||
|                 print repr(val) | ||||
|                 # get the Main Control | ||||
|                 form = api.findTopWindow(wantedText='Microsoft Office Outlook') | ||||
|                 controls = findControls(form) | ||||
|                 # get the check box | ||||
|                 checkBox = findControl(form, wantedText='Zugriff') | ||||
|                 setCheckBox(checkBox, 1) | ||||
|                 # get the combo box | ||||
|                 comboBox = findControl(form, wantedClass='ComboBox') | ||||
|                 items = getComboboxItems(comboBox) | ||||
|                 selectComboboxItem(comboBox, items[3])#'10 Minuten' | ||||
|                 # finally get the button and click it | ||||
|                 button = findControl(form, wantedText = 'Erteilen') | ||||
|                 clickButton(button) | ||||
|                 break | ||||
							
								
								
									
										30
									
								
								agent/system/xmlrpc.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								agent/system/xmlrpc.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Configuration controlled import of twisted xmlrpc functionality | ||||
| 
 | ||||
| $Id: rpcapi.py | ||||
| """ | ||||
| 
 | ||||
| def setup(config): | ||||
|     global xmlrpc | ||||
|     if config.transport.remote.server == 'testing': | ||||
|         from cybertools.agent.testing.rpcserver import xmlrpc | ||||
|     else: | ||||
|         from twisted.web import xmlrpc | ||||
							
								
								
									
										73
									
								
								agent/talk/README.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								agent/talk/README.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
| 
 | ||||
| 
 | ||||
| Communication Handling | ||||
| ====================== | ||||
| 
 | ||||
| Communication services are provided by handlers specified in the ``talk`` | ||||
| package. | ||||
| 
 | ||||
| Set up and start an agent with a server | ||||
| --------------------------------------- | ||||
| 
 | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... talk.server(names=['http']) | ||||
|   ... talk.server.http(port=8081) | ||||
|   ... talk.http(handler='testing') | ||||
|   ... ''' | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
|   Setting up HTTP handler for port 8081. | ||||
| 
 | ||||
|   >>> master.servers | ||||
|   [<cybertools.agent.talk.http.HttpServer object...>] | ||||
| 
 | ||||
| We also provide a class to be used for creating subscribers, i.e. objects | ||||
| that receive messages. | ||||
| 
 | ||||
|   >>> class Subscriber(object): | ||||
|   ...     def __init__(self, name): | ||||
|   ...         self.name = name | ||||
|   ...     def onMessage(self, interaction, data): | ||||
|   ...         print ('%s receiving: interaction=%s, data=%s' % | ||||
|   ...                       (self.name, interaction, data)) | ||||
|   ...         tester.stop() | ||||
| 
 | ||||
|   >>> serverSub = Subscriber('server') | ||||
|   >>> master.servers[0].subscribe(serverSub, 'testing') | ||||
| 
 | ||||
| Set up a client | ||||
| --------------- | ||||
| 
 | ||||
| In order to simplify the testing we do not set up a separate agent to | ||||
| work with the client but handle the client directly. | ||||
| 
 | ||||
|   >>> from cybertools.agent.talk.http import HttpClient | ||||
|   >>> client = HttpClient(master) | ||||
| 
 | ||||
|   >>> clientSub = Subscriber('client') | ||||
| 
 | ||||
|   >>> session = client.connect(clientSub, 'http://localhost:8081/') | ||||
| 
 | ||||
| Run the communication dialog | ||||
| ---------------------------- | ||||
| 
 | ||||
|   >>> tester.run() | ||||
|   client receiving: interaction=None, data={u'status': u'OK'} | ||||
| 
 | ||||
| 
 | ||||
| Fin de Partie | ||||
| ============= | ||||
| 
 | ||||
|   >>> tester.stopThreads() | ||||
							
								
								
									
										93
									
								
								agent/talk/base.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								agent/talk/base.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Handling asynchronous communication tasks - common and base classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.web.client import getPage | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.talk.interfaces import ISession, IInteraction | ||||
| from cybertools.util import json | ||||
| 
 | ||||
| 
 | ||||
| class Session(object): | ||||
| 
 | ||||
|     implements(ISession) | ||||
| 
 | ||||
|     def __init__(self, id, manager, subscriber, url): | ||||
|         self.id = id | ||||
|         self.manager = manager | ||||
|         self.subscriber = subscriber | ||||
|         self.url = url | ||||
|         self.state = 'logon' | ||||
|         self.sending = False | ||||
|         self.queue = [] | ||||
|         self.interactions = {} | ||||
|         self.interactionCount = 0 | ||||
| 
 | ||||
|     def received(self, data): | ||||
|         data = json.loads(data) | ||||
|         # TODO: check data; notify sender? | ||||
|         self.sending = False | ||||
|         self._processQueue() | ||||
| 
 | ||||
|     def send(self, data, interaction): | ||||
|         data['interaction'] = interaction.id | ||||
|         if self.sending or self.queue: | ||||
|             self.queue.append(data) | ||||
|         else: | ||||
|             self._sendData(data) | ||||
| 
 | ||||
|     def processQueue(self): | ||||
|         if not self.queue: | ||||
|             return | ||||
|         self._sendData(self.queue.pop(0)) | ||||
| 
 | ||||
|     def sendData(self, data, command='send'): | ||||
|         self.sending = True | ||||
|         content = dict(id=self.id, command=command, data=data) | ||||
|         d = getPage(self.url, postdata=json.dumps(content)) | ||||
|         d.addCallback(s.received) | ||||
| 
 | ||||
|     def connected(self, data): | ||||
|         data = json.loads(data) | ||||
|         self.state = 'open' | ||||
|         self.subscriber.onMessage(None, data) | ||||
|         self.sending = False | ||||
|         self.processQueue() | ||||
| 
 | ||||
|     def generateInteractionId(self): | ||||
|         self.interactionCount += 1 | ||||
|         return '%07i' % self.interactionCount | ||||
| 
 | ||||
| 
 | ||||
| class Interaction(object): | ||||
| 
 | ||||
|     implements(IInteraction) | ||||
| 
 | ||||
|     finished = False | ||||
| 
 | ||||
|     def __init__(self, session): | ||||
|         self.session = session | ||||
|         self.id = self.session.generateInteractionId() | ||||
|         self.session.interactions[self.id] = self | ||||
| 
 | ||||
							
								
								
									
										181
									
								
								agent/talk/http.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								agent/talk/http.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,181 @@ | |||
| # | ||||
| #  Copyright (c) 2009 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 | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Handling asynchronous and possibly asymmetric communication tasks via HTTP. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from time import time | ||||
| from twisted.web.client import getPage | ||||
| from twisted.web.resource import Resource | ||||
| from twisted.web.server import Site, NOT_DONE_YET | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.components import servers, clients | ||||
| from cybertools.agent.system.http import listener | ||||
| from cybertools.agent.talk.base import Session, Interaction | ||||
| from cybertools.agent.talk.interfaces import IServer, IClient | ||||
| from cybertools.util import json | ||||
| 
 | ||||
| 
 | ||||
| # server implementation | ||||
| 
 | ||||
| #@server | ||||
| class HttpServer(object): | ||||
| 
 | ||||
|     implements(IServer) | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
|         self.port = agent.config.talk.server.http.port | ||||
|         self.subscribers = {} | ||||
|         self.sessions = {} | ||||
|         self.site = Site(RootResource(self)) | ||||
| 
 | ||||
|     def setup(self): | ||||
|         print 'Setting up HTTP handler for port %i.' % self.port | ||||
|         listener.listenTCP(self.port, self.site) | ||||
| 
 | ||||
|     def subscribe(self, subscriber, aspect): | ||||
|         subs = self.subscribers.setdefault(aspect, []) | ||||
|         if subscriber not in subs: | ||||
|             subs.append(subscriber) | ||||
| 
 | ||||
|     def unsubscribe(self, subscriber, aspect): | ||||
|         pass | ||||
| 
 | ||||
|     def send(self, session, data, interaction=None): | ||||
|         if interaction is None: | ||||
|             interaction = Interaction(session) | ||||
|         # check session's queue | ||||
|         # check open poll - write response | ||||
|         return interaction | ||||
| 
 | ||||
|     def process(self, client, data): | ||||
|         action = data.get('action') | ||||
|         if not action: | ||||
|             return self._error('missing action') | ||||
|         amethod = self.actions.get(action) | ||||
|         if amethod is None: | ||||
|             return self._error('illegal action %r' % action) | ||||
|         sid = data.get('session') | ||||
|         if not sid: | ||||
|             return self._error('missing session id') | ||||
|         sessionId = ':'.join((client, sid)) | ||||
|         message = amethod(self, sessionId, client, data) | ||||
|         if message: | ||||
|             return self._error(message) | ||||
|         return '{"status": "OK"}' | ||||
| 
 | ||||
|     def _connect(self, sessionId, client, data): | ||||
|         if sessionId in self.sessions: | ||||
|             return 'duplicate session id %r' % sessionId | ||||
|         self.sessions[sessionId] = HttpServerSession(sessionId, self, None, client) | ||||
|         # TODO: notify subscribers | ||||
| 
 | ||||
|     def _poll(self, sessionId, client, data): | ||||
|         # record deferred with session | ||||
|         return NOT_DONE_YET | ||||
| 
 | ||||
|     def _send(self, sessionId, client, data): | ||||
|         for sub in self.subscribers.values(): | ||||
|             sub.onMessage(data) | ||||
| 
 | ||||
|     def _error(self, message): | ||||
|         return json.dumps(dict(status='error', message=message)) | ||||
| 
 | ||||
|     actions = dict(connect=_connect, poll=_poll, send=_send) | ||||
| 
 | ||||
| servers.register(HttpServer, Master, name='http') | ||||
| 
 | ||||
| 
 | ||||
| class RootResource(Resource): | ||||
| 
 | ||||
|     isLeaf = True | ||||
| 
 | ||||
|     def __init__(self, server): | ||||
|         self.server = server | ||||
| 
 | ||||
|     def render(self, request): | ||||
|         client = request.getClient() | ||||
|         data = json.loads(request.content.read()) | ||||
|         return self.server.process(client, data) | ||||
| 
 | ||||
| 
 | ||||
| class HttpServerSession(Session): | ||||
| 
 | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| # client implementation | ||||
| 
 | ||||
| #@client | ||||
| class HttpClient(object): | ||||
| 
 | ||||
|     implements(IClient) | ||||
| 
 | ||||
|     def __init__(self, agent): | ||||
|         self.agent = agent | ||||
|         self.sessions = {} | ||||
| 
 | ||||
|     def connect(self, subscriber, url, credentials=None): | ||||
|         id = self.generateSessionId() | ||||
|         s = HttpClientSession(self, id, subscriber, url) | ||||
|         self.sessions[id] = s | ||||
|         data = dict(action='connect', session=id) | ||||
|         if credentials is not None: | ||||
|             data.update(credentials) | ||||
|         # s.send(data, None) | ||||
|         d = getPage(url, postdata=json.dumps(data)) | ||||
|         d.addCallback(s.connected) | ||||
|         return s | ||||
| 
 | ||||
|     def disconnect(self, session): | ||||
|         pass | ||||
| 
 | ||||
|     def send(self, session, data, interaction=None): | ||||
|         if interaction is None: | ||||
|             interaction = Interaction(session) | ||||
|         session.send(data, interaction) | ||||
|         return interaction | ||||
| 
 | ||||
|     def generateSessionId(self): | ||||
|         return '%.7f' % time() | ||||
| 
 | ||||
| clients.register(HttpClient, Master, name='http') | ||||
| 
 | ||||
| 
 | ||||
| class HttpClientSession(Session): | ||||
| 
 | ||||
|     def connected(self, data): | ||||
|         super(HttpClientSession, self).connected(data) | ||||
|         # self.poll() | ||||
| 
 | ||||
|     def pollReceived(self, data): | ||||
|         data = json.loads(data) | ||||
|         if data.get('action') != 'idle': | ||||
|             self.subscriber.onMessage(interaction, data) | ||||
|         # self.poll() | ||||
| 
 | ||||
|     def poll(self): | ||||
|         content = dict(id=self.id, command='poll') | ||||
|         d = getPage(self.url, postdata=json.dumps(content)) | ||||
|         d.addCallback(s.pollReceived) | ||||
							
								
								
									
										121
									
								
								agent/talk/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								agent/talk/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Interfaces for handling asynchronous communication tasks. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import Interface, Attribute | ||||
| 
 | ||||
| 
 | ||||
| class IServer(Interface): | ||||
|     """ A server waits for connection requests from a client. A connected | ||||
|         client may then send data to or receive messages from the server. | ||||
|     """ | ||||
| 
 | ||||
|     def subscribe(subscriber, aspect): | ||||
|         """ The subscriber will receive messages via its ``onMesssage`` method. | ||||
| 
 | ||||
|             The aspect is a dotted string used to select the kind of | ||||
|             sessions/remote clients the subscriber wants to receive messages | ||||
|             from. | ||||
|         """ | ||||
| 
 | ||||
|     def unsubscribe(subscriber, aspect): | ||||
|         """ Stop receiving messages. | ||||
|         """ | ||||
| 
 | ||||
|     def send(session, data, interaction=None): | ||||
|         """ Send data to the remote client specified via the session given. | ||||
|             The session has to be created previously by a connect attempt | ||||
|             from the client. | ||||
| 
 | ||||
|             If interaction is None, create a new one. | ||||
|             Return the interaction. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class IClient(Interface): | ||||
|     """ A client initiates a connection (session) to a server and may then | ||||
|         sent data to or receive data from the server. | ||||
|     """ | ||||
| 
 | ||||
|     def connect(subscriber, url, credentials=None): | ||||
|         """ Connect to a server using the URL given, optionally logging in | ||||
|             with the credentials given. | ||||
| 
 | ||||
|             The subscriber will receive messages via its ``onMesssage`` callback. | ||||
| 
 | ||||
|             Return a an ISession implementation that may be used for sending | ||||
|             data to the server. | ||||
|         """ | ||||
| 
 | ||||
|     def disconnect(session): | ||||
|         """ Close the connection for the session given. | ||||
|         """ | ||||
| 
 | ||||
|     def send(session, data, interaction=None): | ||||
|         """ Send data to the server specified via the session given. | ||||
| 
 | ||||
|             If interaction is None, create a new one. | ||||
|             Return the interaction. | ||||
| 
 | ||||
|             Sending an interaction with ``finished`` set to True signifies | ||||
|             the last message of an interaction. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| # auxiliary interfaces | ||||
| 
 | ||||
| class ISubscriber(Interface): | ||||
|     """ May receive message notifications. | ||||
|     """ | ||||
| 
 | ||||
|     def onMessage(interaction, data): | ||||
|         """ Callback method for message notifications. | ||||
|         """ | ||||
| 
 | ||||
|     def onError(interaction, data): | ||||
|         """ Callback method for error notifications. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
| class ISession(Interface): | ||||
|     """ Represents the connection to a server within a client or | ||||
|         a remote client connection within a server. | ||||
|     """ | ||||
| 
 | ||||
|     manager = Attribute("""The server or client object, respectively, that | ||||
|                 created the session.""") | ||||
|     subscriber = Attribute("The subscriber that initiated the session.") | ||||
|     url = Attribute("The URL of the server (or client) the session connects to.") | ||||
|     state = Attribute("""A string specifying the current state of the session: | ||||
|                 'logon': The remote client is trying to connect/log in, | ||||
|                          data may contain credential information; | ||||
|                 'logoff': The remote client is closing the connection; | ||||
|                 'open': The connection is open.""") | ||||
| 
 | ||||
| 
 | ||||
| class IInteraction(Interface): | ||||
|     """ Represents a set of message exchanges belonging together. | ||||
|     """ | ||||
| 
 | ||||
|     session = Attribute("The session the interaction belongs to.") | ||||
|     finished = Attribute("The interaction is finished, interaction data may be cleared.") | ||||
							
								
								
									
										9
									
								
								agent/testing/agent.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								agent/testing/agent.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| # | ||||
| #   Standard configuration for agent application | ||||
| # | ||||
| #   $Id$ | ||||
| # | ||||
| 
 | ||||
| controller(names=['test']) | ||||
| scheduler(name='core') | ||||
| logger(name='default', standard=30) | ||||
							
								
								
									
										43
									
								
								agent/testing/control.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								agent/testing/control.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Controller for testing purposes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.base.control import SampleController, JobSpecification | ||||
| from cybertools.agent.components import controllers | ||||
| from cybertools.agent.crawl.mail import MailResource | ||||
| 
 | ||||
| 
 | ||||
| class Controller(SampleController): | ||||
| 
 | ||||
|     agents = (('tr1', 'transport.remote'),) | ||||
| 
 | ||||
|     def _getCurrentJobs(self): | ||||
|         print '_getCurrentJobs' | ||||
|         return [JobSpecification('sample', '00001', agent='tr1', | ||||
|                         params=dict(resource=MailResource()))] | ||||
| 
 | ||||
| 
 | ||||
| controllers.register(Controller, Master, name='test') | ||||
							
								
								
									
										1
									
								
								agent/testing/data/file1.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								agent/testing/data/file1.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| Data from file1.txt | ||||
							
								
								
									
										1
									
								
								agent/testing/data/subdir/file2.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								agent/testing/data/subdir/file2.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| Data from file2.txt | ||||
							
								
								
									
										44
									
								
								agent/testing/http.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								agent/testing/http.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Fake testing objects/functions for HTTP communication. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.internet.defer import Deferred, succeed | ||||
| 
 | ||||
| 
 | ||||
| class Listener(object): | ||||
| 
 | ||||
|     site = port = None | ||||
| 
 | ||||
|     def listenTCP(self, port, site): | ||||
|         self.port = port | ||||
|         self.site = site | ||||
|         self.resource = site.resource | ||||
|         deferred = self.deferred = Deferred() | ||||
|         return deferred | ||||
| 
 | ||||
| 
 | ||||
| listener = Listener() | ||||
| 
 | ||||
| 
 | ||||
| def getPage(url, contextFactory=None, method='GET', postdata=None, **kwargs): | ||||
|     return succeed('{"message": "OK"}') | ||||
							
								
								
									
										73
									
								
								agent/testing/main.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										73
									
								
								agent/testing/main.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| #! /usr/bin/env python2.4 | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent application. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| from twisted.internet import reactor | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| 
 | ||||
| 
 | ||||
| application = None  # contains application object if started via twistd | ||||
| 
 | ||||
| 
 | ||||
| def getConfig(): | ||||
|     home = os.path.abspath(os.path.dirname(__file__)) | ||||
|     configName = 'agent.cfg' | ||||
|     configFile = open(os.path.join(home, configName)) | ||||
|     config = configFile.read() | ||||
|     configFile.close() | ||||
|     return config | ||||
| 
 | ||||
| 
 | ||||
| def setup(configInfo=None): | ||||
|     if configInfo is None: | ||||
|         configInfo = getConfig() | ||||
|     master = Master(configInfo) | ||||
|     setupEnvironment(master.config) | ||||
|     master.setup() | ||||
|     print 'Starting agent application...' | ||||
|     print 'Using controllers %s.' % ', '.join(master.config.controller.names) | ||||
|     return master | ||||
| 
 | ||||
| 
 | ||||
| def setupEnvironment(config): | ||||
|     from cybertools.agent.base import agent, control, job, log, schedule | ||||
|     from cybertools.agent.core import agent, control, schedule | ||||
|     from cybertools.agent.control import cmdline, remote | ||||
|     from cybertools.agent.transport import remote, loops | ||||
|     from cybertools.agent.testing import control | ||||
|     from cybertools.agent.system.windows import api | ||||
|     api.setup(config) | ||||
|     from cybertools.agent.crawl import base, filesystem, outlook | ||||
| 
 | ||||
| 
 | ||||
| def startReactor(): | ||||
|     reactor.run() | ||||
|     print 'Agent application has been stopped.' | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     setup() | ||||
|     startReactor() | ||||
							
								
								
									
										74
									
								
								agent/testing/main_outlook.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								agent/testing/main_outlook.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| #! /usr/bin/env python2.4 | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent application. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| from twisted.internet import reactor | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| 
 | ||||
| 
 | ||||
| application = None  # contains application object if started via twistd | ||||
| 
 | ||||
| 
 | ||||
| def getConfig(): | ||||
|     agentHome = os.path.abspath(os.path.dirname(__file__)) | ||||
|     configName = 'outlook.cfg' | ||||
|     configFile = open(os.path.join(agentHome, configName)) | ||||
|     config = configFile.read() | ||||
|     configFile.close() | ||||
|     return config | ||||
| 
 | ||||
| 
 | ||||
| def setup(configInfo=None): | ||||
|     if configInfo is None: | ||||
|         configInfo = getConfig() | ||||
|     master = Master(configInfo) | ||||
|     setupEnvironment(master.config) | ||||
|     master.setup() | ||||
|     print 'Starting agent application...' | ||||
|     print 'Using controllers %s.' % ', '.join(master.config.controller.names) | ||||
|     return master | ||||
| 
 | ||||
| 
 | ||||
| def setupEnvironment(config): | ||||
|     from cybertools.agent.base import agent, control, job, log, schedule | ||||
|     from cybertools.agent.core import agent, control, schedule | ||||
|     from cybertools.agent.control import cmdline | ||||
|     from cybertools.agent.system.windows import api | ||||
|     api.setup(config) | ||||
|     from cybertools.agent.crawl import base, outlook | ||||
| 
 | ||||
| 
 | ||||
| def startReactor(): | ||||
|     reactor.run() | ||||
|     print 'Agent application has been stopped.' | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     master = setup() | ||||
|     controller = master.controllers[0] | ||||
|     controller.createAgent('crawl.outlook', 'sample02') | ||||
|     controller.enterJob('sample', 'sample02', params=dict(inbox=True)) | ||||
|     startReactor() | ||||
							
								
								
									
										82
									
								
								agent/testing/main_transport.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								agent/testing/main_transport.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | |||
| #! /usr/bin/env python2.4 | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Agent application. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| from twisted.internet import reactor | ||||
| 
 | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.crawl.base import Metadata, Resource | ||||
| 
 | ||||
| 
 | ||||
| application = None  # contains application object if started via twistd | ||||
| 
 | ||||
| 
 | ||||
| def getConfig(): | ||||
|     agentHome = os.path.abspath(os.path.dirname(__file__)) | ||||
|     configName = 'transporter.cfg' | ||||
|     configFile = open(os.path.join(agentHome, configName)) | ||||
|     config = configFile.read() | ||||
|     configFile.close() | ||||
|     return config | ||||
| 
 | ||||
| 
 | ||||
| def setup(configInfo=None): | ||||
|     if configInfo is None: | ||||
|         configInfo = getConfig() | ||||
|     master = Master(configInfo) | ||||
|     setupEnvironment(master.config) | ||||
|     master.setup() | ||||
|     print 'Starting agent application...' | ||||
|     print 'Using controllers %s.' % ', '.join(master.config.controller.names) | ||||
|     return master | ||||
| 
 | ||||
| 
 | ||||
| def setupEnvironment(config): | ||||
|     from cybertools.agent.base import agent, control, job, log, schedule | ||||
|     from cybertools.agent.core import agent, control, schedule | ||||
|     from cybertools.agent.control import cmdline | ||||
|     from cybertools.agent.system import rpcapi | ||||
|     rpcapi.setup(config) | ||||
|     from cybertools.agent.system import sftpapi | ||||
|     sftpapi.setup(config) | ||||
|     from cybertools.agent.transport import remote | ||||
| 
 | ||||
| 
 | ||||
| def startReactor(): | ||||
|     reactor.run() | ||||
|     print 'Agent application has been stopped.' | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     master = setup() | ||||
|     controller = master.controllers[0] | ||||
|     controller.createAgent('transport.remote', 'sample03') | ||||
|     metadata01 = Metadata(dict(filename='dummy.txt')) | ||||
|     res01 = Resource() | ||||
|     res01.metadata = metadata01 | ||||
|     res01.path = 'data/file1.txt' | ||||
|     controller.enterJob('sample', 'sample03', params=dict(resource=res01)) | ||||
|     startReactor() | ||||
							
								
								
									
										10
									
								
								agent/testing/outlook.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								agent/testing/outlook.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| # | ||||
| #   Standard configuration for agent application | ||||
| # | ||||
| #   $Id: outlook.cfg 2496 2008-04-04 08:07:22Z helmutm $ | ||||
| # | ||||
| 
 | ||||
| controller(names=['core.sample']) | ||||
| scheduler(name='core') | ||||
| logger(name='default', standard=30) | ||||
| system.winapi = 'use_outlook' | ||||
							
								
								
									
										135
									
								
								agent/testing/rpcserver.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								agent/testing/rpcserver.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,135 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Fake rpcserver for testing purposes | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.internet.defer import succeed | ||||
| 
 | ||||
| 
 | ||||
| class RPCServer(object): | ||||
| 
 | ||||
|     serverURL = '' | ||||
|     method = '' | ||||
|     machineName = '' | ||||
|     userName = '' | ||||
|     password = '' | ||||
|     controller = '' | ||||
| 
 | ||||
|     def __init__(self, serverURL = '', method = '', machineName = '', | ||||
|                  userName = '', password = '', controlObj= None): | ||||
|         self.serverURL = serverURL | ||||
|         self.method = method | ||||
|         self.machineName = machineName | ||||
|         self.userName = userName | ||||
|         self.password = password | ||||
|         self.controller = controlObj | ||||
| 
 | ||||
|     def getMetadata(self, metadata): | ||||
|         if self.controller is not None: | ||||
|             # pass metadata to controller | ||||
|             # this is done AFTER the resource (like e.g. file or mail) | ||||
|             # is handed over | ||||
|             pass | ||||
|         deferred = succeed('Metadata accepted by server') | ||||
|         return deferred | ||||
| 
 | ||||
|     def xmlrpc_shutdownRPCServer(): | ||||
|         return "xmlrRPC server shutdown completed!" | ||||
| 
 | ||||
| 
 | ||||
| class XmlRpc(object): | ||||
| 
 | ||||
|      Proxy = None | ||||
|      XMLRPC = None | ||||
|      Handler = None | ||||
|      XMLRPCIntrospection = None | ||||
|      QueryProtocol = None | ||||
|      _QueryFactory = None | ||||
| 
 | ||||
|      def __init__(self): | ||||
|          self.Proxy = Proxy | ||||
|          #self.XMLRPC = XMLRPC() | ||||
|          #self.Handler = Handler() | ||||
|          #self.XMLRPCIntrospection = XMLRPCIntrospection() | ||||
|          #self.QueryProtocol = QueryProtocol() | ||||
|          #self._QueryFactory = _QueryFactory() | ||||
| 
 | ||||
|      def addIntrospection(self, xmlrpc): | ||||
|          pass | ||||
| 
 | ||||
| 
 | ||||
| class Proxy(object): | ||||
| 
 | ||||
|      url = '' | ||||
|      user = None | ||||
|      password = None | ||||
|      allowNone = False | ||||
|      queryFactory = None | ||||
| 
 | ||||
|      def __init__(self, url, user=None, password=None, allowNone=False): | ||||
|          self.url = url | ||||
|          self.user = user | ||||
|          self.password = password | ||||
|          self.allowNone = allowNone | ||||
|          self.RPCServer = RPCServer() | ||||
| 
 | ||||
|      def callRemote(self, methodName, *params): | ||||
|         """ | ||||
|         intended to simulate the callRemote command of a real xmlrpcserver | ||||
|         that takes a method name and calls the method, returning the results | ||||
|         as xml formatted strings | ||||
|         """ | ||||
|         method = getattr(self.RPCServer, methodName) | ||||
|         return method(*params) | ||||
| 
 | ||||
| 
 | ||||
| xmlrpc = XmlRpc() | ||||
| 
 | ||||
| 
 | ||||
| class XMLRPC(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class Handler(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class XMLRPCIntrospection(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class QueryProtocol(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class _QueryFactory(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
							
								
								
									
										37
									
								
								agent/testing/sftp.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								agent/testing/sftp.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Fake sftp class for testing purposes | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.internet.defer import succeed | ||||
| 
 | ||||
| 
 | ||||
| class FileTransfer(object): | ||||
| 
 | ||||
| 
 | ||||
|     def __init__(self,  host, port, username, password): | ||||
|         pass | ||||
| 
 | ||||
|     def upload(self, localPath, remotePath): | ||||
|         deferred = succeed('Upload completed') | ||||
|         return deferred | ||||
| 
 | ||||
							
								
								
									
										80
									
								
								agent/testing/test_rpcserver.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								agent/testing/test_rpcserver.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Providing access for remote agent instances by listening for requests | ||||
| from remote transport agents. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.web import xmlrpc, server, resource | ||||
| from twisted.internet import defer, reactor | ||||
| from cybertools.agent.base.agent import Agent | ||||
| 
 | ||||
| application = None | ||||
| 
 | ||||
| class RPCServer(xmlrpc.XMLRPC): | ||||
| 
 | ||||
|     serverURL = '' | ||||
|     method = '' | ||||
|     machineName = '' | ||||
|     userName = '' | ||||
|     password = '' | ||||
|     controller = '' | ||||
|     close = reactor.stop | ||||
| 
 | ||||
|     def __init__(self, serverURL = '', method = '', machineName = '', | ||||
|                  userName = '', password = '', controlObj= None): | ||||
|         self.serverURL = serverURL | ||||
|         self.method = method | ||||
|         self.machineName = machineName | ||||
|         self.userName = userName | ||||
|         self.password = password | ||||
|         self.controller = controlObj | ||||
|         xmlrpc.XMLRPC.__init__(self) | ||||
| 
 | ||||
|     def xmlrpc_transfer(self, resource): | ||||
|         if self.controller is not None: | ||||
|             # pass resource object to controller | ||||
|             # this is done BEFORE the metadata is handed over | ||||
|             # call notify method of controller | ||||
|             pass | ||||
|         print resource | ||||
|         return "Resource received: ", resource | ||||
| 
 | ||||
|     def xmlrpc_getMetadata(self, metadata): | ||||
|         if self.controller is not None: | ||||
|             # pass metadata to controller | ||||
|             # this is done AFTER the resource (like e.g. file or mail) | ||||
|             # is handed over | ||||
|             pass | ||||
|         print '*** metadata', metadata | ||||
|         metadata = "Echo: ", metadata | ||||
|         return metadata | ||||
| 
 | ||||
|     def xmlrpc_shutdownRPCServer(): | ||||
|         self.close() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     from twisted.internet import reactor | ||||
|     site = RPCServer() | ||||
|     reactor.listenTCP(8082, server.Site(site)) | ||||
|     print '*** listening...' | ||||
|     reactor.run() | ||||
							
								
								
									
										18
									
								
								agent/testing/test_sftp.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								agent/testing/test_sftp.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| 
 | ||||
| 
 | ||||
| from twisted.internet import reactor | ||||
| 
 | ||||
| from cybertools.agent.transport.file.sftp import FileTransfer | ||||
| 
 | ||||
| def output(x): | ||||
|     print x | ||||
| 
 | ||||
| ft = FileTransfer('cy05.de', 22, 'scrat', '...') | ||||
| 
 | ||||
| d = ft.upload('d:\\text2.rtf', 'text.txt') | ||||
| d.addCallback(output) | ||||
| 
 | ||||
| reactor.callLater(21, ft.close) | ||||
| reactor.callLater(32, reactor.stop) | ||||
| 
 | ||||
| reactor.run() | ||||
							
								
								
									
										17
									
								
								agent/testing/transporter.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								agent/testing/transporter.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| # | ||||
| #   sample.cfg - agent configuration for demonstration and testing purposes | ||||
| # | ||||
| #   $Id$ | ||||
| # | ||||
| # transportserver.xmlrpc='testing' | ||||
| 
 | ||||
| controller(names=['core.sample']) | ||||
| scheduler(name='core') | ||||
| logger(name='default', standard=30) | ||||
| #transport.remote.server = 'testing' | ||||
| transport.remote.url = 'http://localhost:8082' | ||||
| transport.remote.ftp.url = 'cy05.de' | ||||
| transport.remote.ftp.user = 'scrat' | ||||
| transport.remote.ftp.password = '...' | ||||
| transport.remote.sftp = 'http://cy05.de' | ||||
| transport.remote.chunksize = 4096 | ||||
							
								
								
									
										240
									
								
								agent/testing/winapi.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								agent/testing/winapi.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,240 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Fake Windows API functions for testing purposes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| win32api = win32process = win32con = None | ||||
| 
 | ||||
| 
 | ||||
| class com_error(Exception): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class Attachments(list): | ||||
|      | ||||
|     elemCount = 0 | ||||
|     data = [] | ||||
| 
 | ||||
|     def __init__(self, params=[]): | ||||
|         for elem in params: | ||||
|             fileitem = Attachment(filename=elem[0], ParentMail=elem[1]) | ||||
|             self.data.append(fileitem) | ||||
|             print "Attachment: ", fileitem.FileName | ||||
| 
 | ||||
|     @property | ||||
|     def Application(self): | ||||
|         print "Outlook application instance" | ||||
|         return "Outlook application instance" | ||||
|      | ||||
|     def Item(self, index): | ||||
|         return self.data[index-1] | ||||
|      | ||||
|     @property | ||||
|     def count(self): | ||||
|         return len(data) | ||||
|      | ||||
|     def __len__(self): | ||||
|         return len(self.data) | ||||
|      | ||||
|     def __iter__(self): | ||||
|         yield self.data | ||||
|          | ||||
|     def __getitem__(self, idx): | ||||
|         return self.data[idx] | ||||
| 
 | ||||
|      | ||||
| class Attachment(object): | ||||
|      | ||||
|     File = "" | ||||
|     parentMailObject = None | ||||
|      | ||||
|     def __init__(self, ParentMail, filename=""): | ||||
|         self.File = filename | ||||
|         self.parentMailObject = ParentMail | ||||
|      | ||||
|     def SaveAsFile(self, path=""): | ||||
|         print "Attachment saved" | ||||
|      | ||||
|     @property | ||||
|     def Parent(self): | ||||
|         " return value of Attribute Parent is of type _MailItem" | ||||
|         return self.parentMailObject | ||||
|      | ||||
|     @property | ||||
|     def Type(self): | ||||
|         pass | ||||
|      | ||||
|     @property | ||||
|     def Size(self): | ||||
|         # the size property is not available in Outlook 2000 | ||||
|         pass | ||||
|      | ||||
|     @property | ||||
|     def Application(self): | ||||
|         " Actual instance of Outlook application" | ||||
|         return None | ||||
|      | ||||
|     @property | ||||
|     def FileName(self): | ||||
|         return self.File | ||||
|      | ||||
|      | ||||
| class Mail(object): | ||||
| 
 | ||||
|     #this is just a guess what a Outlook Mail Object Probably returns | ||||
|     #Class = client.constants.olMail | ||||
| 
 | ||||
|     def __init__(self, subj="", sendName="", to="", body="", **kw): | ||||
|         self.Class = client.constants.olMail | ||||
|         self.Subject = subj | ||||
|         self.SenderName = sendName | ||||
|         self.To = to | ||||
|         self.Body = body | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
|              | ||||
|     def addAttachment(self, **kw): | ||||
|         """ | ||||
|         this is a method which probably does not exist in a real mail | ||||
|         Currently this is a work around to add attachments to a mail | ||||
|         """ | ||||
|         for k, v in kw.items(): | ||||
|             setattr(self, k, v) | ||||
| 
 | ||||
|     @property | ||||
|     def _prop_map_get_(self): | ||||
|         #here it is necessary of what attributes (called keys in outlok.py) | ||||
|         #an Outlook Mail typically has | ||||
|         return self.__dict__ | ||||
| 
 | ||||
| 
 | ||||
| class Items(object): | ||||
|      | ||||
|     temp = {} | ||||
|     data = [] | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.data.append(Mail(subj="Python Training", | ||||
|                             sendName="Mark Pilgrim", | ||||
|                             to="allPythonics@python.org", | ||||
|                             body="The training will take place on Wed, 21st Dec.\ | ||||
|                                   Kindly check the enclosed invitation.", | ||||
|                             BodyFormat=1 | ||||
|                             )) | ||||
|         self.data[0].addAttachment(Attachments=Attachments([("Invitation.pdf", self.data[0]), ("21.pdf", self.data[0])])) | ||||
|         self.data.append(Mail(subj="Information Technolgies Inc. Test it!", | ||||
|                             sendName="IT.org", | ||||
|                             to="allUser@internet.com", | ||||
|                             BodyFormat=2, | ||||
|                             HTMLBody="<html>\ | ||||
|                                         <head>\ | ||||
|                                         <title>Test-HTML-Mail</title>\ | ||||
|                                         </head>\ | ||||
|                                         <body>\ | ||||
|                                         <h1>Das ist eine HTML-Mail</h1>\ | ||||
|                                         <div align='center'>Hier steht \ | ||||
|                                         <b>Beispiel</b>-Text</div>\ | ||||
|                                         </body>\ | ||||
|                                       </html>", | ||||
|                             SentOn="21.04.07" | ||||
|                             )) | ||||
|         self.data.append(Mail(subj="@ Product Details @", | ||||
|                             sendName="", | ||||
|                             senderEmailAddress="custominfo@enterprise.com", | ||||
|                             to="recipient1@mail.com, customer@web.de", | ||||
|                             BodyFormat=1, | ||||
|                             body="Dear customer,\ | ||||
|                                   Hereby we submit you the information you ordered.\ | ||||
|                                   Please feel free to ask anytime you want.\ | ||||
|                                   Sincerely, Customer Support", | ||||
|                             SentOn="30.07.07" | ||||
|                             )) | ||||
| 
 | ||||
|     def Item(self, idx): | ||||
|         return self.data[idx-1] | ||||
| 
 | ||||
|     def __len__(self): | ||||
|         return len(self.data) | ||||
| 
 | ||||
| 
 | ||||
| class OutlookFolder(object): | ||||
| 
 | ||||
|     # Folders defines in Outlook the sub folders under the "Main" Folder | ||||
|     Folders = None | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         print "collecting Mails from folder" | ||||
|         self.Items = Items() | ||||
| 
 | ||||
| 
 | ||||
| class OutlookNamespace(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
|     def GetDefaultFolder(self, message=""): | ||||
|         print "retrieving Outlook default folder" | ||||
|         folder = OutlookFolder() | ||||
|         return folder | ||||
| 
 | ||||
| 
 | ||||
| class OutlookApp(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
|     def GetNamespace(self, message=""): | ||||
|         print "Namespace " + message + " retrieved" | ||||
|         oNamespace = OutlookNamespace() | ||||
|         return oNamespace | ||||
| 
 | ||||
| 
 | ||||
| class Message(object): | ||||
| 
 | ||||
|     olFolderInbox = None | ||||
|     # esp. for olMail, for further dummy implementations it is necessary | ||||
|     # to find out, what class is expected. Meaning what type of object has | ||||
|     # to be faked and what attributes it has. see outlook.py | ||||
|     # loadMailsfromFolder | ||||
|     olMail = Mail | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
|     def EnsureDispatch(self, message=""): | ||||
|         print message + " retrieved" | ||||
|         oApp = OutlookApp() | ||||
|         return oApp | ||||
| 
 | ||||
| 
 | ||||
| class client(object): | ||||
| 
 | ||||
|     gencache = Message() | ||||
|     constants = Message() | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
| 
 | ||||
| class ctypes(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         pass | ||||
							
								
								
									
										74
									
								
								agent/tests.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										74
									
								
								agent/tests.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| #! /usr/bin/env python | ||||
| 
 | ||||
| # $Id$ | ||||
| 
 | ||||
| import os, time | ||||
| import unittest, doctest | ||||
| from zope.testing.doctestunit import DocFileSuite | ||||
| from twisted.internet import reactor | ||||
| #from twisted.internet.defer import Deferred | ||||
| #from twisted.trial import unittest as trial_unittest | ||||
| 
 | ||||
| baseDir = os.path.dirname(__file__) | ||||
| 
 | ||||
| 
 | ||||
| class Tester(object): | ||||
|     """ Used for controlled execution of reactor iteration cycles. | ||||
|     """ | ||||
| 
 | ||||
|     stopped = False | ||||
| 
 | ||||
|     def iterate(self, n=10, delay=0): | ||||
|         self.stopped = False | ||||
|         for i in range(n): | ||||
|             if self.stopped: | ||||
|                 return | ||||
|             reactor.iterate(delay) | ||||
| 
 | ||||
|     def run(self, maxduration=1.0, delay=0): | ||||
|         self.stopped = False | ||||
|         end = time.time() + maxduration | ||||
|         while not self.stopped: | ||||
|             reactor.iterate(delay) | ||||
|             if time.time() >= end: | ||||
|                 return | ||||
| 
 | ||||
|     def stop(self): | ||||
|         self.stopped = True | ||||
| 
 | ||||
|     def stopThreads(self): | ||||
|         reactor.threadpool.stop() | ||||
|         reactor.threadpool = None | ||||
| 
 | ||||
| tester = Tester() | ||||
| 
 | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
|     "Basic tests for the cybertools.agent package." | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         pass | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         pass | ||||
| 
 | ||||
|     def testBasicStuff(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| def test_suite(): | ||||
|     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||||
|     testSuite = unittest.TestSuite(( | ||||
|             unittest.makeSuite(Test), | ||||
|             DocFileSuite('README.txt', optionflags=flags), | ||||
|             DocFileSuite('crawl/README.txt', optionflags=flags), | ||||
|             DocFileSuite('crawl/filesystem.txt', optionflags=flags), | ||||
|             DocFileSuite('crawl/outlook.txt', optionflags=flags), | ||||
|             DocFileSuite('transport/transporter.txt', optionflags=flags), | ||||
|             DocFileSuite('talk/README.txt', optionflags=flags), | ||||
|     )) | ||||
|     return testSuite | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     standard_unittest.main(defaultTest='test_suite') | ||||
							
								
								
									
										160
									
								
								agent/transport/file/sftp.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								agent/transport/file/sftp.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,160 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Transferring files to a remote site via SFTP. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.conch.ssh import channel, common, connection | ||||
| from twisted.conch.ssh import filetransfer, transport, userauth | ||||
| from twisted.internet import defer, protocol, reactor | ||||
| 
 | ||||
| CHUNKSIZE = 4096 | ||||
| 
 | ||||
| 
 | ||||
| class FileTransfer(protocol.ClientFactory): | ||||
|     """ Transfers files to a remote SCP/SFTP server. | ||||
|     """ | ||||
|     channel = None | ||||
| 
 | ||||
|     def __init__(self, host, port, username, password): | ||||
|         self.username = username | ||||
|         self.password = password | ||||
|         self.queue = [] | ||||
|         reactor.connectTCP(host, port, self) | ||||
| 
 | ||||
|     def buildProtocol(self, addr): | ||||
|         protocol = self.protocol = ClientTransport(self) | ||||
|         return protocol | ||||
| 
 | ||||
|     def upload(self, localPath, remotePath): | ||||
|         """ Copies a file, returning a deferred. | ||||
|         """ | ||||
|         d = self.deferred = defer.Deferred() | ||||
|         # we put everything in a queue so that more than one file may | ||||
|         # be transferred in one connection. | ||||
|         self.queue.append(dict(deferred=d, | ||||
|                                command='upload', | ||||
|                                localPath=localPath, | ||||
|                                remotePath=remotePath)) | ||||
|         if len(self.queue) == 1 and self.channel is not None: | ||||
|             # the channel has emptied the queue | ||||
|             self.channel.execute() | ||||
|         return d | ||||
| 
 | ||||
|     def close(self): | ||||
|         # TODO: put in queue... | ||||
|         self.protocol.transport.loseConnection() | ||||
|         print 'connection closed' | ||||
| 
 | ||||
| 
 | ||||
| class SFTPChannel(channel.SSHChannel): | ||||
|     """ An SSH channel using the SFTP subsystem for transferring files | ||||
|         and issuing other filesystem requests. | ||||
|     """ | ||||
| 
 | ||||
|     name = 'session' | ||||
|     remFile = '' | ||||
|     remOffset = 0 | ||||
| 
 | ||||
|     def channelOpen(self, data): | ||||
|         d = self.conn.sendRequest(self, 'subsystem', common.NS('sftp'), wantReply=1) | ||||
|         d.addCallback(self.channelOpened) | ||||
| 
 | ||||
|     def channelOpened(self, data): | ||||
|         self.client = filetransfer.FileTransferClient() | ||||
|         self.client.makeConnection(self) | ||||
|         self.dataReceived = self.client.dataReceived | ||||
|         self.execute() | ||||
|         self.conn.factory.channel = self | ||||
| 
 | ||||
|     def execute(self): | ||||
|         queue = self.conn.factory.queue | ||||
|         if queue: | ||||
|             command = queue.pop() | ||||
|             commandName = command.pop('command') | ||||
|             method = getattr(self, 'command_' + commandName, None) | ||||
|             if method is not None: | ||||
|                 self.params = command | ||||
|                 method() | ||||
| 
 | ||||
|     def command_upload(self): | ||||
|         params = self.params | ||||
|         remotePath = params['remotePath'] | ||||
|         localPath = params['localPath'] | ||||
|         self.localFile = open(localPath, 'rb') | ||||
|         d = self.client.openFile(remotePath, | ||||
|                     filetransfer.FXF_WRITE | filetransfer.FXF_CREAT, {}) | ||||
|         d.addCallbacks(self.writeChunk, self.logError) | ||||
| 
 | ||||
|     def writeChunk(self, remoteFile): | ||||
|         if isinstance(remoteFile, tuple) == False: | ||||
|             self.remFile = remoteFile | ||||
|         data = self.localFile.read(CHUNKSIZE) | ||||
|         if len(data) < CHUNKSIZE: | ||||
|             self.d = self.remFile.writeChunk(self.remOffset, data) | ||||
|             self.d.addCallbacks(self.finished, self.logError) | ||||
|         else: | ||||
|             self.d = self.remFile.writeChunk(self.remOffset, data) | ||||
|             self.remOffset = self.remOffset + CHUNKSIZE | ||||
|             self.d.addCallbacks(self.writeChunk, self.logError) | ||||
| 
 | ||||
|     def logError(self, reason): | ||||
|         print 'error', reason | ||||
| 
 | ||||
|     def finished(self, result): | ||||
|         self.localFile.close() | ||||
|         self.remFile.close() | ||||
|         #self.d.callback('finished') | ||||
|         self.conn.factory.deferred.callback('finished') | ||||
| 
 | ||||
| # classes for managing the SSH protocol and connection | ||||
| 
 | ||||
| class ClientTransport(transport.SSHClientTransport): | ||||
| 
 | ||||
|     def __init__(self, factory): | ||||
|         self.factory = factory | ||||
| 
 | ||||
|     def verifyHostKey(self, pubKey, fingerprint): | ||||
|         # this is insecure!!! | ||||
|         return defer.succeed(True) | ||||
| 
 | ||||
|     def connectionSecure(self): | ||||
|         self.requestService(UserAuth(self.factory, ClientConnection(self.factory))) | ||||
| 
 | ||||
| 
 | ||||
| class ClientConnection(connection.SSHConnection): | ||||
| 
 | ||||
|     def __init__(self, factory): | ||||
|         connection.SSHConnection.__init__(self) | ||||
|         self.factory = factory | ||||
| 
 | ||||
|     def serviceStarted(self): | ||||
|         self.openChannel(SFTPChannel(conn=self)) | ||||
| 
 | ||||
| 
 | ||||
| class UserAuth(userauth.SSHUserAuthClient): | ||||
| 
 | ||||
|     def __init__(self, factory, connection): | ||||
|         userauth.SSHUserAuthClient.__init__(self, factory.username, connection) | ||||
|         self.password = factory.password | ||||
| 
 | ||||
|     def getPassword(self, prompt=None): | ||||
|         return defer.succeed(self.password) | ||||
							
								
								
									
										27
									
								
								agent/transport/loops.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								agent/transport/loops.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Transferring information to a loops site on a local Zope instance. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										92
									
								
								agent/transport/remote.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								agent/transport/remote.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Transferring information to or requesting information from a remote | ||||
| cybertools.agent instance by transferring files to the remote system | ||||
| and sending requests to a corresponding remote controller. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from twisted.internet import defer | ||||
| from zope.interface import implements | ||||
| import os | ||||
| 
 | ||||
| from cybertools.agent.system import xmlrpc | ||||
| from cybertools.agent.system import sftp | ||||
| from cybertools.agent.base.agent import Master | ||||
| from cybertools.agent.core.agent import QueueableAgent | ||||
| from cybertools.agent.interfaces import ITransporter | ||||
| from cybertools.agent.crawl.base import Metadata | ||||
| from cybertools.agent.crawl.mail import MailResource | ||||
| from cybertools.agent.crawl.filesystem import FileResource | ||||
| from cybertools.agent.components import agents | ||||
| from cybertools.util.config import Configurator | ||||
| 
 | ||||
| 
 | ||||
| class Transporter(QueueableAgent): | ||||
| 
 | ||||
|     implements(ITransporter) | ||||
| 
 | ||||
|     port = 22 | ||||
|     machineName = '' | ||||
| 
 | ||||
|     def __init__(self, master): | ||||
|         super(Transporter, self).__init__(master) | ||||
|         config = master.config | ||||
|         serverURL = config.transport.remote.url | ||||
|         self.server = xmlrpc.xmlrpc.Proxy(serverURL) | ||||
|         userName = config.transport.remote.ftp.user | ||||
|         password = config.transport.remote.ftp.password | ||||
|         host = config.transport.remote.ftp.url | ||||
|         self.ftpServer = sftp.FileTransfer(host, self.port, userName, password) | ||||
| 
 | ||||
|     def process(self): | ||||
|         return self.transfer(self.params['resource']) | ||||
| 
 | ||||
|     def transfer(self, resource): | ||||
|         """ Transfer the resource (an object providing IResource) | ||||
|             to the server and return a Deferred. | ||||
|         """ | ||||
|         self.deferred = defer.Deferred() | ||||
|         remoteFile = os.path.basename(resource.path) | ||||
|         d = self.ftpServer.upload(resource.path, remoteFile) | ||||
|         d.addErrback(self.errorHandler) | ||||
|         d.addCallback(lambda result: | ||||
|                 self.server.callRemote('getMetadata', dict(resource.metadata))) | ||||
|         d.addCallback(self.transferDone) | ||||
|         return self.deferred | ||||
| 
 | ||||
|     def errorHandler(self, errorInfo): | ||||
|         """ | ||||
|         Invoked as a callback from self.transfer | ||||
|         Error handler. | ||||
|         """ | ||||
|         print errorInfo | ||||
|         #self.server.close() | ||||
| 
 | ||||
|     def transferDone(self, result): | ||||
|         """ | ||||
|         Invoked as a callback from self.transfer | ||||
|         This callback method is called when resource and metadata | ||||
|         have been transferred successfully. | ||||
|         """ | ||||
|         self.deferred.callback(result) | ||||
| 
 | ||||
| agents.register(Transporter, Master, name='transport.remote') | ||||
							
								
								
									
										52
									
								
								agent/transport/transporter.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								agent/transport/transporter.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| ================================================ | ||||
| Agents for Job Execution and Communication Tasks | ||||
| ================================================ | ||||
| 
 | ||||
|   ($Id$) | ||||
| 
 | ||||
|   >>> config = ''' | ||||
|   ... controller(names=['core.sample']) | ||||
|   ... scheduler(name='core') | ||||
|   ... logger(name='default', standard=30) | ||||
|   ... transport.remote.server = 'testing' | ||||
|   ... transport.remote.sftp = 'testing' | ||||
|   ... transport.remote.url = 'http://localhost:8123' | ||||
|   ... ''' | ||||
|   >>> from cybertools.agent.main import setup | ||||
|   >>> master = setup(config) | ||||
|   Starting agent application... | ||||
|   Using controllers core.sample. | ||||
| 
 | ||||
| 
 | ||||
| Transporter | ||||
| =========== | ||||
| 
 | ||||
| The agent uses Twisted's cooperative multitasking model. | ||||
| 
 | ||||
| The Transporter is used to contact an xmlrpc Server and transmit the metadata | ||||
| to the other loops system. The Transporter is derived from Queueable agent | ||||
| to ensure that only one item at a time is transmitted. | ||||
| 
 | ||||
| Returns a deferred that must be supplied with a callback method (and in | ||||
| most cases also an errback method). | ||||
| 
 | ||||
| This Testcase is using subsidiary methods to simulate a real xmlrpc server. | ||||
| 
 | ||||
|   >>> controller = master.controllers[0] | ||||
|   >>> controller.createAgent('transport.remote', 'sample03') | ||||
| 
 | ||||
| In the next step we request the start of a job, again via the controller. | ||||
| 
 | ||||
|   >>> from cybertools.agent.crawl.base import Metadata, Resource | ||||
|   >>> md01 = Metadata(dict(filename='dummy.txt')) | ||||
|   >>> r01 = Resource() | ||||
|   >>> r01.metadata = md01 | ||||
|   >>> r01.path = 'resource.txt' | ||||
|   >>> controller.enterJob('sample', 'sample03', params=dict(resource=r01)) | ||||
| 
 | ||||
| The job is not executed immediately - we have to hand over control to | ||||
| the twisted reactor first. | ||||
| 
 | ||||
|   >>> from cybertools.agent.tests import tester | ||||
|   >>> tester.iterate() | ||||
|   Job 00001 completed; result: Metadata accepted by server; | ||||
							
								
								
									
										368
									
								
								agent/util/task.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								agent/util/task.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,368 @@ | |||
| # -*- test-case-name: twisted.test.test_task -*- | ||||
| # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | ||||
| # See LICENSE for details. | ||||
| 
 | ||||
| 
 | ||||
| """Scheduling utility methods and classes. | ||||
| 
 | ||||
| API Stability: Unstable | ||||
| 
 | ||||
| @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | ||||
| """ | ||||
| 
 | ||||
| __metaclass__ = type | ||||
| 
 | ||||
| import time | ||||
| 
 | ||||
| from twisted.python.runtime import seconds | ||||
| from twisted.python import reflect | ||||
| 
 | ||||
| from twisted.internet import base, defer | ||||
| 
 | ||||
| 
 | ||||
| class LoopingCall: | ||||
|     """Call a function repeatedly. | ||||
| 
 | ||||
|     @ivar f: The function to call. | ||||
|     @ivar a: A tuple of arguments to pass the function. | ||||
|     @ivar kw: A dictionary of keyword arguments to pass to the function. | ||||
| 
 | ||||
|     If C{f} returns a deferred, rescheduling will not take place until the | ||||
|     deferred has fired. The result value is ignored. | ||||
|     """ | ||||
| 
 | ||||
|     call = None | ||||
|     running = False | ||||
|     deferred = None | ||||
|     interval = None | ||||
|     count = None | ||||
|     starttime = None | ||||
| 
 | ||||
|     def _callLater(self, delay): | ||||
|         from twisted.internet import reactor | ||||
|         return reactor.callLater(delay, self) | ||||
| 
 | ||||
|     _seconds = staticmethod(seconds) | ||||
| 
 | ||||
|     def __init__(self, f, *a, **kw): | ||||
|         self.f = f | ||||
|         self.a = a | ||||
|         self.kw = kw | ||||
| 
 | ||||
|     def start(self, interval, now=True): | ||||
|         """Start running function every interval seconds. | ||||
| 
 | ||||
|         @param interval: The number of seconds between calls.  May be | ||||
|         less than one.  Precision will depend on the underlying | ||||
|         platform, the available hardware, and the load on the system. | ||||
| 
 | ||||
|         @param now: If True, run this call right now.  Otherwise, wait | ||||
|         until the interval has elapsed before beginning. | ||||
| 
 | ||||
|         @return: A Deferred whose callback will be invoked with | ||||
|         C{self} when C{self.stop} is called, or whose errback will be | ||||
|         invoked when the function raises an exception or returned a | ||||
|         deferred that has its errback invoked. | ||||
|         """ | ||||
|         assert not self.running, ("Tried to start an already running " | ||||
|                                   "LoopingCall.") | ||||
|         if interval < 0: | ||||
|             raise ValueError, "interval must be >= 0" | ||||
|         self.running = True | ||||
|         d = self.deferred = defer.Deferred() | ||||
|         self.starttime = self._seconds() | ||||
|         self.count = 0 | ||||
|         self.interval = interval | ||||
|         if now: | ||||
|             self() | ||||
|         else: | ||||
|             self._reschedule() | ||||
|         return d | ||||
| 
 | ||||
|     def stop(self): | ||||
|         """Stop running function. | ||||
|         """ | ||||
|         assert self.running, ("Tried to stop a LoopingCall that was " | ||||
|                               "not running.") | ||||
|         self.running = False | ||||
|         if self.call is not None: | ||||
|             self.call.cancel() | ||||
|             self.call = None | ||||
|             d, self.deferred = self.deferred, None | ||||
|             d.callback(self) | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         def cb(result): | ||||
|             if self.running: | ||||
|                 self._reschedule() | ||||
|             else: | ||||
|                 d, self.deferred = self.deferred, None | ||||
|                 d.callback(self) | ||||
| 
 | ||||
|         def eb(failure): | ||||
|             self.running = False | ||||
|             d, self.deferred = self.deferred, None | ||||
|             d.errback(failure) | ||||
| 
 | ||||
|         self.call = None | ||||
|         d = defer.maybeDeferred(self.f, *self.a, **self.kw) | ||||
|         d.addCallback(cb) | ||||
|         d.addErrback(eb) | ||||
| 
 | ||||
|     def _reschedule(self): | ||||
|         if self.interval == 0: | ||||
|             self.call = self._callLater(0) | ||||
|             return | ||||
| 
 | ||||
|         fromNow = self.starttime - self._seconds() | ||||
| 
 | ||||
|         while self.running: | ||||
|             self.count += 1 | ||||
|             fromStart = self.count * self.interval | ||||
|             delay = fromNow + fromStart | ||||
|             if delay > 0: | ||||
|                 self.call = self._callLater(delay) | ||||
|                 return | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         if hasattr(self.f, 'func_name'): | ||||
|             func = self.f.func_name | ||||
|             if hasattr(self.f, 'im_class'): | ||||
|                 func = self.f.im_class.__name__ + '.' + func | ||||
|         else: | ||||
|             func = reflect.safe_repr(self.f) | ||||
| 
 | ||||
|         return 'LoopingCall<%r>(%s, *%s, **%s)' % ( | ||||
|             self.interval, func, reflect.safe_repr(self.a), | ||||
|             reflect.safe_repr(self.kw)) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class SchedulerStopped(Exception): | ||||
|     """ | ||||
|     The operation could not complete because the scheduler was stopped in | ||||
|     progress or was already stopped. | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class _Timer(object): | ||||
|     MAX_SLICE = 0.01 | ||||
|     def __init__(self): | ||||
|         self.end = time.time() + self.MAX_SLICE | ||||
| 
 | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         return time.time() >= self.end | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| _EPSILON = 0.00000001 | ||||
| def _defaultScheduler(x): | ||||
|     from twisted.internet import reactor | ||||
|     return reactor.callLater(_EPSILON, x) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class Cooperator(object): | ||||
|     """ | ||||
|     Cooperative task scheduler. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, | ||||
|                  terminationPredicateFactory=_Timer, | ||||
|                  scheduler=_defaultScheduler, | ||||
|                  started=True): | ||||
|         """ | ||||
|         Create a scheduler-like object to which iterators may be added. | ||||
| 
 | ||||
|         @param terminationPredicateFactory: A no-argument callable which will | ||||
|         be invoked at the beginning of each step and should return a | ||||
|         no-argument callable which will return False when the step should be | ||||
|         terminated.  The default factory is time-based and allows iterators to | ||||
|         run for 1/100th of a second at a time. | ||||
| 
 | ||||
|         @param scheduler: A one-argument callable which takes a no-argument | ||||
|         callable and should invoke it at some future point.  This will be used | ||||
|         to schedule each step of this Cooperator. | ||||
| 
 | ||||
|         @param started: A boolean which indicates whether iterators should be | ||||
|         stepped as soon as they are added, or if they will be queued up until | ||||
|         L{Cooperator.start} is called. | ||||
|         """ | ||||
|         self.iterators = [] | ||||
|         self._metarator = iter(()) | ||||
|         self._terminationPredicateFactory = terminationPredicateFactory | ||||
|         self._scheduler = scheduler | ||||
|         self._delayedCall = None | ||||
|         self._stopped = False | ||||
|         self._started = started | ||||
| 
 | ||||
| 
 | ||||
|     def coiterate(self, iterator, doneDeferred=None): | ||||
|         """ | ||||
|         Add an iterator to the list of iterators I am currently running. | ||||
| 
 | ||||
|         @return: a Deferred that will fire when the iterator finishes. | ||||
|         """ | ||||
|         if doneDeferred is None: | ||||
|             doneDeferred = defer.Deferred() | ||||
|         if self._stopped: | ||||
|             doneDeferred.errback(SchedulerStopped()) | ||||
|             return doneDeferred | ||||
|         self.iterators.append((iterator, doneDeferred)) | ||||
|         self._reschedule() | ||||
|         return doneDeferred | ||||
| 
 | ||||
| 
 | ||||
|     def _tasks(self): | ||||
|         terminator = self._terminationPredicateFactory() | ||||
|         while self.iterators: | ||||
|             for i in self._metarator: | ||||
|                 yield i | ||||
|                 if terminator(): | ||||
|                     return | ||||
|             self._metarator = iter(self.iterators) | ||||
| 
 | ||||
| 
 | ||||
|     def _tick(self): | ||||
|         """ | ||||
|         Run one scheduler tick. | ||||
|         """ | ||||
|         self._delayedCall = None | ||||
|         for taskObj in self._tasks(): | ||||
|             iterator, doneDeferred = taskObj | ||||
|             try: | ||||
|                 result = iterator.next() | ||||
|             except StopIteration: | ||||
|                 self.iterators.remove(taskObj) | ||||
|                 doneDeferred.callback(iterator) | ||||
|             except: | ||||
|                 self.iterators.remove(taskObj) | ||||
|                 doneDeferred.errback() | ||||
|             else: | ||||
|                 if isinstance(result, defer.Deferred): | ||||
|                     self.iterators.remove(taskObj) | ||||
|                     def cbContinue(result, taskObj=taskObj): | ||||
|                         self.coiterate(*taskObj) | ||||
|                     result.addCallbacks(cbContinue, doneDeferred.errback) | ||||
|         self._reschedule() | ||||
| 
 | ||||
| 
 | ||||
|     _mustScheduleOnStart = False | ||||
|     def _reschedule(self): | ||||
|         if not self._started: | ||||
|             self._mustScheduleOnStart = True | ||||
|             return | ||||
|         if self._delayedCall is None and self.iterators: | ||||
|             self._delayedCall = self._scheduler(self._tick) | ||||
| 
 | ||||
| 
 | ||||
|     def start(self): | ||||
|         """ | ||||
|         Begin scheduling steps. | ||||
|         """ | ||||
|         self._stopped = False | ||||
|         self._started = True | ||||
|         if self._mustScheduleOnStart: | ||||
|             del self._mustScheduleOnStart | ||||
|             self._reschedule() | ||||
| 
 | ||||
| 
 | ||||
|     def stop(self): | ||||
|         """ | ||||
|         Stop scheduling steps.  Errback the completion Deferreds of all | ||||
|         iterators which have been added and forget about them. | ||||
|         """ | ||||
|         self._stopped = True | ||||
|         for iterator, doneDeferred in self.iterators: | ||||
|             doneDeferred.errback(SchedulerStopped()) | ||||
|         self.iterators = [] | ||||
|         if self._delayedCall is not None: | ||||
|             self._delayedCall.cancel() | ||||
|             self._delayedCall = None | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| _theCooperator = Cooperator() | ||||
| def coiterate(iterator): | ||||
|     """ | ||||
|     Cooperatively iterate over the given iterator, dividing runtime between it | ||||
|     and all other iterators which have been passed to this function and not yet | ||||
|     exhausted. | ||||
|     """ | ||||
|     return _theCooperator.coiterate(iterator) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class Clock: | ||||
|     """ | ||||
|     Provide a deterministic, easily-controlled implementation of | ||||
|     L{IReactorTime.callLater}.  This is commonly useful for writing | ||||
|     deterministic unit tests for code which schedules events using this API. | ||||
|     """ | ||||
|     rightNow = 0.0 | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.calls = [] | ||||
| 
 | ||||
|     def seconds(self): | ||||
|         """ | ||||
|         Pretend to be time.time().  This is used internally when an operation | ||||
|         such as L{IDelayedCall.reset} needs to determine a a time value | ||||
|         relative to the current time. | ||||
| 
 | ||||
|         @rtype: C{float} | ||||
|         @return: The time which should be considered the current time. | ||||
|         """ | ||||
|         return self.rightNow | ||||
| 
 | ||||
| 
 | ||||
|     def callLater(self, when, what, *a, **kw): | ||||
|         """ | ||||
|         See L{twisted.internet.interfaces.IReactorTime.callLater}. | ||||
|         """ | ||||
|         self.calls.append( | ||||
|             base.DelayedCall(self.seconds() + when, | ||||
|                              what, a, kw, | ||||
|                              self.calls.remove, | ||||
|                              lambda c: None, | ||||
|                              self.seconds)) | ||||
|         self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime())) | ||||
|         return self.calls[-1] | ||||
| 
 | ||||
| 
 | ||||
|     def advance(self, amount): | ||||
|         """ | ||||
|         Move time on this clock forward by the given amount and run whatever | ||||
|         pending calls should be run. | ||||
| 
 | ||||
|         @type amount: C{float} | ||||
|         @param amount: The number of seconds which to advance this clock's | ||||
|         time. | ||||
|         """ | ||||
|         self.rightNow += amount | ||||
|         while self.calls and self.calls[0].getTime() <= self.seconds(): | ||||
|             call = self.calls.pop(0) | ||||
|             call.called = 1 | ||||
|             call.func(*call.args, **call.kw) | ||||
| 
 | ||||
| 
 | ||||
|     def pump(self, timings): | ||||
|         """ | ||||
|         Advance incrementally by the given set of times. | ||||
| 
 | ||||
|         @type timings: iterable of C{float} | ||||
|         """ | ||||
|         for amount in timings: | ||||
|             self.advance(amount) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| __all__ = [ | ||||
|     'LoopingCall', | ||||
| 
 | ||||
|     'Clock', | ||||
| 
 | ||||
|     'SchedulerStopped', 'Cooperator', 'coiterate', | ||||
|     ] | ||||
|  | @ -1,10 +1,28 @@ | |||
| # cybertools.ajax.dojo.layout | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz - helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| """ | ||||
| Embed Dojo using the cybertools.composer.layout procedure. | ||||
| 
 | ||||
| """ Embed Dojo using the cybertools.composer.layout procedure. | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from io import StringIO | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from cStringIO import StringIO | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| 
 | ||||
| from cybertools.browser.renderer import RendererFactory | ||||
|  | @ -1,18 +1,38 @@ | |||
| # cybertools.brain.neuron | ||||
| # | ||||
| #  Copyright (c) 2006 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 simple basic implementation of Neuron and Synapsis. | ||||
| """ | ||||
| A simple basic implementation of Neuron and Synapsis. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implementer | ||||
| from zope.interface import implements | ||||
| from cybertools.brain.interfaces import INeuron, ISynapsis | ||||
| from cybertools.brain.state import State, Transition | ||||
| 
 | ||||
| 
 | ||||
| @implementer(ISynapsis) | ||||
| class Synapsis(object): | ||||
|     """ A synapsis connects two neurons. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ISynapsis) | ||||
| 
 | ||||
|     def __init__(self, sender, receiver): | ||||
|         self.sender = sender | ||||
|         sender.receivers.append(self) | ||||
|  | @ -26,9 +46,10 @@ class Synapsis(object): | |||
|         receiver.notify(session) | ||||
| 
 | ||||
| 
 | ||||
| @implementer(INeuron) | ||||
| class Neuron(object): | ||||
| 
 | ||||
|     implements(INeuron) | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.senders = [] | ||||
|         self.receivers = [] | ||||
							
								
								
									
										41
									
								
								brain/session.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								brain/session.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| # | ||||
| #  Copyright (c) 2006 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 | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Transaction management. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| from cybertools.brain.interfaces import ISession | ||||
| 
 | ||||
| 
 | ||||
| class Session(object): | ||||
| 
 | ||||
|     implements(ISession) | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.states = {} | ||||
| 
 | ||||
|     def setState(self, neuron, state): | ||||
|         self.states[neuron] = state | ||||
| 
 | ||||
|     def getState(self, neuron): | ||||
|         return self.states.get(neuron, neuron.state) | ||||
| 
 | ||||
							
								
								
									
										55
									
								
								brain/state.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								brain/state.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| # | ||||
| #  Copyright (c) 2006 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 | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| Base classes for state and state manipulations using a float-based state. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import implements | ||||
| from cybertools.brain.interfaces import IState, ITransition | ||||
| 
 | ||||
| 
 | ||||
| class State(object): | ||||
|     """ The state of a neuron. | ||||
|     """ | ||||
| 
 | ||||
|     implements(IState) | ||||
| 
 | ||||
|     def __init__(self, value=0.0): | ||||
|         self.value = value | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return '<State %0.1f>' % self.value | ||||
| 
 | ||||
| 
 | ||||
| class Transition(object): | ||||
| 
 | ||||
|     implements(ITransition) | ||||
| 
 | ||||
|     def __init__(self, synapsis, factor=1.0): | ||||
|         self.synapsis = synapsis | ||||
|         self.factor = factor | ||||
| 
 | ||||
|     def execute(self, session=None): | ||||
|         oldState = self.synapsis.receiver.getState(session) | ||||
|         senderState = self.synapsis.sender.getState(session) | ||||
|         return State(oldState.value + senderState.value * self.factor) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1,6 +1,7 @@ | |||
| # cybertools.brain.tests | ||||
| # $Id$ | ||||
| 
 | ||||
| import unittest, doctest | ||||
| from zope.testing.doctestunit import DocFileSuite | ||||
| from zope.interface.verify import verifyClass | ||||
| 
 | ||||
| from cybertools.brain.interfaces import INeuron, ISynapsis | ||||
|  | @ -19,8 +20,9 @@ class TestBrain(unittest.TestCase): | |||
| def test_suite(): | ||||
|     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||||
|     return unittest.TestSuite(( | ||||
|         unittest.TestLoader().loadTestsFromTestCase(TestBrain), | ||||
|         doctest.DocFileSuite('README.txt', optionflags=flags,), | ||||
|                 unittest.makeSuite(TestBrain), | ||||
|                 DocFileSuite('README.txt', | ||||
|                              optionflags=flags,), | ||||
|            )) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|  | @ -3,7 +3,7 @@ Browser View Tools | |||
| ================== | ||||
| 
 | ||||
|   >>> from zope import component, interface | ||||
|   >>> from zope.interface import Interface, implementer | ||||
|   >>> from zope.interface import Interface, implements | ||||
|   >>> from zope.publisher.interfaces.browser import IBrowserRequest | ||||
| 
 | ||||
| 
 | ||||
|  | @ -17,10 +17,8 @@ the common and node modules there.) | |||
| 
 | ||||
| Let's start with a dummy content object and create a view on it: | ||||
| 
 | ||||
|   >>> #@implementer(Interface) | ||||
|   >>> class SomeObject(object): | ||||
|   ... 	  pass | ||||
|   >>> SomeObject = implementer(Interface)(SomeObject) | ||||
|   ...     implements(Interface) | ||||
|   >>> obj = SomeObject() | ||||
| 
 | ||||
|   >>> from cybertools.browser.view import GenericView | ||||
|  | @ -124,7 +122,7 @@ ZPT macros: | |||
|   >>> len(cssMacros) | ||||
|   4 | ||||
|   >>> m1 = cssMacros[0] | ||||
|   >>> print(m1.name, m1.media, m1.resourceName) | ||||
|   >>> print m1.name, m1.media, m1.resourceName | ||||
|   css all zope3_tablelayout.css | ||||
| 
 | ||||
| Calling a macro provided by Controller.macros[] returns the real ZPT macro: | ||||
|  | @ -140,7 +138,7 @@ The pre-set collection of macros for a certain slot may be extended | |||
|   >>> len(controller.macros['css']) | ||||
|   5 | ||||
|   >>> m5 = controller.macros['css'][4] | ||||
|   >>> print(m5.name, m5.media, m5.resourceName) | ||||
|   >>> print m5.name, m5.media, m5.resourceName | ||||
|   css all node.css | ||||
| 
 | ||||
| If an identifier is given (the second parameter) a certain macro is only | ||||
|  | @ -223,7 +221,7 @@ controller issues a redirect. | |||
|   >>> from cybertools.browser.form import IFormController, FormController | ||||
|   >>> class MyController(FormController): | ||||
|   ...     def update(self): | ||||
|   ...         print('updating...') | ||||
|   ...         print 'updating...' | ||||
|   ...         return True | ||||
| 
 | ||||
|   >>> component.provideAdapter(MyController, (View, IBrowserRequest), | ||||
|  | @ -1,12 +1,31 @@ | |||
| # cybertools.browser.action | ||||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ Base classes (sort of views) for action portlet items. | ||||
| """ | ||||
| Base classes (sort of views) for action portlet items. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from copy import copy | ||||
| from urllib.parse import urlencode | ||||
| from urllib import urlencode | ||||
| from zope import component | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| 
 | ||||
| action_macros = ViewPageTemplateFile('action_macros.pt') | ||||
| Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 655 B | 
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		
		Reference in a new issue