[Checkins] SVN: lovely.remotetask/ Initial import of a remote task service. Here is an excerpt from the

Stephan Richter srichter at cosmos.phy.tufts.edu
Mon Aug 14 10:42:15 EDT 2006


Log message for revision 69474:
  Initial import of a remote task service. Here is an excerpt from the 
  README:
  
  This package provides an implementation of a remote task execution Web 
  service that allows to execute pre-defined tasks on another server. 
  Those services are useful in two ways:
  
  1. They enable us to complete tasks that are not natively available on a
     particular machine. For example, it is not possible to convert an AVI 
     file to a Flash(R) movie using Linux, the operating system our Web 
     server might run on.
  
  2. They also allow to move expensive operations to other servers. This is
     valuable, for example, when converting videos on high-traffic sites.
  
  I think the API is pretty okay, but we will have to gain some experience 
  with it first. While I have done some minimal stress testing on my 
  machine (mainly ensuring that we do not get ZODB conflicts), it has not 
  been used in production yet.
  
  I also have a derivative package specifically for transcoding videos. If 
  people are interested in it, I am willing to upload it.
  
  

Changed:
  A   lovely.remotetask/
  A   lovely.remotetask/branches/
  A   lovely.remotetask/tags/
  A   lovely.remotetask/trunk/
  A   lovely.remotetask/trunk/src/
  A   lovely.remotetask/trunk/src/lovely/
  A   lovely.remotetask/trunk/src/lovely/__init__.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/
  A   lovely.remotetask/trunk/src/lovely/remotetask/DEPENDENCIES.cfg
  A   lovely.remotetask/trunk/src/lovely/remotetask/README.txt
  A   lovely.remotetask/trunk/src/lovely/remotetask/SETUP.cfg
  A   lovely.remotetask/trunk/src/lovely/remotetask/__init__.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/__init__.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/ftests.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/configure.zcml
  A   lovely.remotetask/trunk/src/lovely/remotetask/ftests.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/job.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/lovely.remotetask-configure.zcml
  A   lovely.remotetask/trunk/src/lovely/remotetask/service.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/task.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/tests.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.txt
  A   lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.zcml

-=-
Added: lovely.remotetask/trunk/src/lovely/__init__.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/__init__.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/__init__.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: lovely.remotetask/trunk/src/lovely/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/DEPENDENCIES.cfg
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/DEPENDENCIES.cfg	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/DEPENDENCIES.cfg	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,3 @@
+zope.app
+zc.table
+zc.queue

Added: lovely.remotetask/trunk/src/lovely/remotetask/README.txt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/README.txt	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/README.txt	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,119 @@
+=====================
+Remote Task Execution
+=====================
+
+This package provides an implementation of a remote task execution Web service
+that allows to execute pre-defined tasks on another server. Those services are
+useful in two ways:
+
+1. They enable us to complete tasks that are not natively available on a
+   particular machine. For example, it is not possible to convert an AVI file
+   to a Flash(R) movie using Linux, the operating system our Web server might
+   run on.
+
+2. They also allow to move expensive operations to other servers. This is
+   valuable, for example, when converting videos on high-traffic sites.
+
+Let's now start by creating a single service:
+
+  >>> from lovely import remotetask
+  >>> service = remotetask.TaskService()
+
+We can discover the available tasks:
+
+  >>> service.getAvailableTasks()
+  {}
+
+This list is initially empty, because we have not registered any tasks. Let's
+now define a task that simply echos an input string:
+
+  >>> def echo(input):
+  ...     return input
+
+  >>> import lovely.remotetask.task
+  >>> echoTask = remotetask.task.SimpleTask(echo)
+
+The only API requirement on the converter is to be callable. Now we make sure
+that the task works:
+
+  >>> echoTask(service, input={'foo': 'blah'})
+  {'foo': 'blah'}
+
+Let's now register the task as a utility:
+
+  >>> import zope.component
+  >>> zope.component.provideUtility(echoTask, name='echo')
+
+The echo task is now available in the service:
+
+  >>> service.getAvailableTasks()
+  {u'echo': <SimpleTask <function echo ...>>}
+
+
+Since the service cannot instantaneously complete a task, incoming jobs are
+managed by a queue. First we request the echo task to be executed:
+
+  >>> jobid = service.add(u'echo', {'foo': 'bar'})
+  >>> jobid
+  1
+
+The ``add()`` function schedules the task called "echo" to be executed with
+the specified arguments. The method returns a job id with which we can inquire
+about the job.
+
+  >>> service.getStatus(jobid)
+  'queued'
+
+Since the job has not been processed, the status is set to "queued". Further,
+there is no result available yet:
+
+  >>> service.getResult(jobid) is None
+  True
+
+As long as the job is not being processed, it can be cancelled:
+
+  >>> service.cancel(jobid)
+  >>> service.getStatus(jobid)
+  'cancelled'
+
+Let's now readd a job:
+
+  >>> jobid = service.add(u'echo', {'foo': 'bar'})
+
+The jobs in the queue are processed by calling the service's ``process()``
+method:
+
+  >>> service.process()
+
+This method is usually called by other application logic, but we have to call
+it manually here, since none of the other infrastructure is setup.
+
+  >>> service.getStatus(jobid)
+  'completed'
+  >>> service.getResult(jobid)
+  {'foo': 'bar'}
+
+Now, let's define a new task that causes an error:
+
+  >>> def error(input):
+  ...     raise remotetask.task.TaskError('An error occurred.')
+
+  >>> zope.component.provideUtility(
+  ...     remotetask.task.SimpleTask(error), name='error')
+
+Now add and execute it:
+
+  >>> jobid = service.add(u'error')
+  >>> service.process()
+
+Let's now see what happened:
+
+  >>> service.getStatus(jobid)
+  'error'
+  >>> service.getError(jobid)
+  'An error occurred.'
+
+For management purposes, the service also allows you to inspect all jobs:
+
+  >>> dict(service.jobs)
+  {1: <Job 1>, 2: <Job 2>, 3: <Job 3>}


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/SETUP.cfg
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/SETUP.cfg	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/SETUP.cfg	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,3 @@
+<data-files zopeskel/etc/package-includes>
+  lovely.remotetask-*.zcml
+</data-files>

Added: lovely.remotetask/trunk/src/lovely/remotetask/__init__.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/__init__.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/__init__.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,2 @@
+# Make a package.
+from lovely.remotetask.service import TaskService


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,96 @@
+==================================
+Task Service Browser Management UI
+==================================
+
+Let's start a browser:
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+Now we add a task service:
+
+  >>> browser.open('http://localhost/manage')
+  >>> browser.getLink('Remote Task Service').click()
+  >>> browser.getControl(name='new_value').value = 'tasks'
+  >>> browser.getControl('Apply').click()
+
+Now let's have a look at the job's table:
+
+  >>> browser.getLink('tasks').click()
+
+You can see the available tasks:
+
+  >>> 'Available Tasks' in browser.contents
+  True
+
+By default there is an "echo" task:
+
+  >>> '<li>echo</li>' in browser.contents
+  True
+
+Below you see a table of all the jobs. Initially we have no jobs, so let's add
+one via XML-RPC:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>add</methodName>
+  ... <params>
+  ... <value><string>echo</string></value>
+  ... <value><struct>
+  ... <key><string>foo</string></key>
+  ... <value><string>bar</string></value>
+  ... </struct></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+
+If we now refresh the screen, we will see the new job:
+
+  >>> browser.reload()
+  >>> print browser.contents
+  <!DOCTYPE ...
+  ...
+  <tr class="odd">
+    <td>
+      <input type="checkbox" name="jobs:list" value="1">
+    </td>
+    <td>
+      1
+    </td>
+    <td>
+      echo
+    </td>
+    <td>
+      queued
+    </td>
+    <td>
+      ...
+    </td>
+    <td>
+      [not set]
+    </td>
+    <td>
+      [not set]
+    </td>
+  </tr>
+  ...
+
+Finally, you can cancel scheduled jobs:
+
+  >>> browser.getControl('Cancel').click()
+  >>> 'No jobs were selected.' in browser.contents
+  True
+
+  >>> browser.getControl(name='jobs:list').getControl(value='1').click()
+  >>> browser.getControl('Cancel').click()
+  >>> 'Jobs were successfully cancelled.' in browser.contents
+  True


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/__init__.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/__init__.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/__init__.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1 @@
+# Make a package


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,19 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser">
+
+  <addMenuItem
+      class="..service.TaskService"
+      title="Remote Task Service"
+      description="A Remote Task Service"
+      permission="zope.ManageContent"
+      />
+
+  <page
+      name="jobs.html"
+      for="..interfaces.ITaskService"
+      class=".service.JobsOverview"
+      permission="zope.ManageContent"
+      menu="zmi_views" title="Jobs"
+      />
+
+</configure>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/ftests.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/ftests.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/ftests.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+import unittest
+from zope.app.testing import functional
+
+def test_suite():
+    return unittest.TestSuite((
+        functional.FunctionalDocFileSuite('README.txt'),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/ftests.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,50 @@
+<html metal:use-macro="context/@@standard_macros/view"
+      i18n:domain="lovely.remotetask">
+<body>
+<div metal:fill-slot="body">
+
+<form action="" method="post"
+      tal:attributes="action request/URL">
+
+  <div class="row">
+    <div class="controls">
+      <input type="submit" class="button" name="STARTPROCESSING"
+             value="Start Processing"
+             tal:condition="not:context/isProcessing"
+             i18n:attributes="value" />
+      <input type="submit" class="button" name="STOPPROCESSING"
+             value="Stop Processing"
+             tal:condition="context/isProcessing"
+             i18n:attributes="value" />
+    </div>
+  </div>
+
+  <p i18n:translate="">
+    Available Tasks:
+  </p>
+  <ul>
+    <li tal:repeat="task view/getAvailableTasks"
+        tal:content="task" />
+  </ul>
+
+  <div class="message"
+       tal:condition="view/status"
+       tal:content="view/status"
+       i18n:translate="">
+    Something happened.
+  </div>
+
+  <tal:block tal:replace="structure view/table" />
+
+  <div class="row">
+    <div class="controls">
+      <input type="submit" class="button" name="CANCEL" value="Cancel"
+             i18n:attributes="value" />
+    </div>
+  </div>
+
+</form>
+
+</div>
+</body>
+</html>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Management Views
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.interface
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.publisher.browser import BrowserPage
+from zc.table import column, table
+from zc.table.interfaces import ISortableColumn
+
+class CheckboxColumn(column.Column):
+    """Provide a column to select applications."""
+
+    def renderCell(self, item, formatter):
+        widget = (u'<input type="checkbox" name="jobs:list" value="%i">')
+        return widget %item.id
+
+class DatetimeColumn(column.GetterColumn):
+    zope.interface.implements(ISortableColumn)
+
+    def renderCell(self, item, formatter):
+        date = self.getter(item, formatter)
+        dformat = formatter.request.locale.dates.getFormatter(
+            'dateTime', 'short')
+        return date and dformat.format(date) or '[not set]'
+
+class JobsOverview(BrowserPage):
+
+    template = ViewPageTemplateFile('jobs.pt')
+    status = None
+
+    columns = (
+        CheckboxColumn(u'Sel'),
+        column.GetterColumn(u'Id', lambda x, f: str(x.id), name='id'),
+        column.GetterColumn(u'Task', lambda x, f: x.task, name='task'),
+        column.GetterColumn(u'Status', lambda x, f: x.status, name='status'),
+        DatetimeColumn(u'Creation Date',
+                       lambda x, f: x.created, name='created'),
+        DatetimeColumn(u'Start Date',
+                       lambda x, f: x.started, name='start'),
+        DatetimeColumn(u'Completion Date',
+                       lambda x, f: x.completed, name='completed'),
+        )
+
+    def table(self):
+        formatter = table.StandaloneFullFormatter(
+            self.context, self.request, self.context.jobs.values(),
+            prefix='jobs.', columns=self.columns)
+        return formatter()
+
+    def getAvailableTasks(self):
+        return sorted(self.context.getAvailableTasks().keys())
+
+    def update(self):
+        if 'STARTPROCESSING' in self.request:
+            self.context.startProcessing()
+        elif 'STOPPROCESSING' in self.request:
+            self.context.stopProcessing()
+        elif 'CANCEL' in self.request:
+            if 'jobs' in self.request:
+                for id in self.request['jobs']:
+                    self.context.cancel(int(id))
+                self.status = 'Jobs were successfully cancelled.'
+            else:
+                self.status = u'No jobs were selected.'
+
+    def __call__(self):
+        self.update()
+        return self.template()


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/configure.zcml
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/configure.zcml	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/configure.zcml	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,30 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="lovely.remotetask">
+
+  <class class=".service.TaskService">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable" />
+    <require
+        permission="zope.Public"
+        interface=".interfaces.ITaskService" />
+  </class>
+
+  <class class=".job.Job">
+    <implements
+        interface="zope.annotation.interfaces.IAttributeAnnotatable" />
+    <require
+        permission="zope.Public"
+        interface=".interfaces.IJob"
+        set_schema=".interfaces.IJob" />
+  </class>
+
+  <!-- Demo: Echo Task -->
+  <utility
+      factory=".task.EchoTask"
+      name="echo" />
+
+  <include package=".browser" />
+  <include file="xmlrpc.zcml" />
+
+</configure>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/ftests.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/ftests.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/ftests.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+import unittest
+from zope.app.testing import functional
+
+def test_suite():
+    return unittest.TestSuite((
+        functional.FunctionalDocFileSuite('xmlrpc.txt'),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/ftests.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,161 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Interfaces
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+import zope.interface
+import zope.interface.common.mapping
+import zope.schema
+from zope.app.container.interfaces import IContained
+
+QUEUED = 'queued'
+PROCESSING = 'processing'
+CANCELLED = 'cancelled'
+ERROR = 'error'
+COMPLETED = 'completed'
+
+class ITaskService(IContained):
+    """A service for managing and executing long-running, remote tasks."""
+
+    jobs = zope.schema.Object(
+        title=u'Jobs',
+        description=u'A mapping of all jobs by job id.',
+        schema=zope.interface.common.mapping.IMapping)
+
+    def getAvailableTasks():
+        """Return a mapping of task name to the task."""
+
+    def add(task, input):
+        """Add a new job for the specified task.
+
+        The task argument is a string specifying the task. The input are
+        arguments for the task.
+        """
+
+    def cancel(jobid):
+        """Cancel a particular job."""
+
+    def getStatus(jobid):
+        """Get the status of a job."""
+
+    def getResult(jobid):
+        """Get the result data structure of the job."""
+
+    def getError(jobid):
+        """Get the error of the job."""
+
+    def processNext():
+        """Process the next job in the queue."""
+
+    def process():
+        """Process all scheduled jobs.
+
+        This call blocks the thread it is running in.
+        """
+
+    def startProcessing():
+        """Start processing jobs.
+
+        This method has to be called after every server restart.
+        """
+
+    def stopProcessing():
+        """Stop processing jobs."""
+
+    def isProcessing():
+        """Check whether the jobs are being processed.
+
+        Return a boolean representing the state.
+        """
+
+
+class ITask(zope.interface.Interface):
+    """A task available in the task service"""
+
+    inputSchema = zope.schema.Object(
+        title=u'Input Schema',
+        description=u'A schema describing the task input signature.',
+        schema=zope.interface.Interface,
+        required=False)
+
+    outputSchema = zope.schema.Object(
+        title=u'Output Schema',
+        description=u'A schema describing the task output signature.',
+        schema=zope.interface.Interface,
+        required=False)
+
+    def __call__(self, service, input):
+        """Execute the task.
+
+        The service argument is the task service object. It allows access to
+        service wide data and the system as a whole.
+
+        The input object must conform to the input schema (if specified). The
+        return value must conform to the output schema.
+        """
+
+
+class IJob(zope.interface.Interface):
+    """An internal job object."""
+
+    id = zope.schema.Int(
+        title=u'Id',
+        description=u'The job id.',
+        required=True)
+
+    task = zope.schema.TextLine(
+        title=u'Task',
+        description=u'The task to be completed.',
+        required=True)
+
+    status = zope.schema.Choice(
+        title=u'Status',
+        description=u'The current status of the job.',
+        values=[QUEUED, PROCESSING, CANCELLED, ERROR, COMPLETED],
+        required=True)
+
+    input = zope.schema.Object(
+        title=u'Input',
+        description=u'The input for the task.',
+        schema=zope.interface.Interface,
+        required=False)
+
+    output = zope.schema.Object(
+        title=u'Output',
+        description=u'The output of the task.',
+        schema=zope.interface.Interface,
+        required=False,
+        default=None)
+
+    error = zope.schema.Object(
+        title=u'Error',
+        description=u'The error object when the task failed.',
+        schema=zope.interface.Interface,
+        required=False,
+        default=None)
+
+    created = zope.schema.Datetime(
+        title=u'Creation Date',
+        description=u'The date/time at which the job was created.',
+        required=True)
+
+    started = zope.schema.Datetime(
+        title=u'Start Date',
+        description=u'The date/time at which the job was started.')
+
+    completed = zope.schema.Datetime(
+        title=u'Completion Date',
+        description=u'The date/time at which the job was completed.')


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/job.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/job.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/job.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import datetime
+import persistent
+import zope.interface
+from zope.schema.fieldproperty import FieldProperty
+from lovely.remotetask import interfaces
+
+
+class Job(persistent.Persistent):
+    """A simple, non-persistent task implementation."""
+    zope.interface.implements(interfaces.IJob)
+
+    id = FieldProperty(interfaces.IJob['id'])
+    task = FieldProperty(interfaces.IJob['task'])
+    input = FieldProperty(interfaces.IJob['input'])
+    output = FieldProperty(interfaces.IJob['output'])
+    error = FieldProperty(interfaces.IJob['error'])
+    created = FieldProperty(interfaces.IJob['created'])
+    started = FieldProperty(interfaces.IJob['started'])
+    completed = FieldProperty(interfaces.IJob['completed'])
+
+    def __init__(self, id, task, input):
+        self.id = id
+        self.task = task
+        self.input = input
+        self.created = datetime.datetime.now()
+
+    def __repr__(self):
+        return '<%s %r>' %(self.__class__.__name__, self.id)


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/job.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/lovely.remotetask-configure.zcml
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/lovely.remotetask-configure.zcml	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/lovely.remotetask-configure.zcml	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1 @@
+<include package="lovely.remotetask" />


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/lovely.remotetask-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/service.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/service.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/service.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,166 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import datetime
+import persistent
+import threading
+import time
+import transaction
+import zc.queue
+import zope.component
+import zope.interface
+import zope.publisher.base
+import zope.publisher.publish
+from BTrees.IOBTree import IOBTree
+from zope.app import zapi
+from zope.app.container import contained
+from zope.app.publication.zopepublication import ZopePublication
+from lovely.remotetask import interfaces, job, task
+
+
+class TaskService(contained.Contained, persistent.Persistent):
+    """A persistent task service.
+
+    The available tasks for this service are managed as utilities.
+    """
+    zope.interface.implements(interfaces.ITaskService)
+
+    taskInterface = interfaces.ITask
+
+    def __init__(self):
+        super(TaskService, self).__init__()
+        self._counter = 1
+        self.jobs = IOBTree()
+        self._queue = zc.queue.PersistentQueue()
+
+    def getAvailableTasks(self):
+        """See interfaces.ITaskService"""
+        return dict(zope.component.getUtilitiesFor(self.taskInterface))
+
+    def add(self, task, input=None):
+        """See interfaces.ITaskService"""
+        if task not in self.getAvailableTasks():
+            raise ValueError('Task does not exist')
+        jobid = self._counter
+        self._counter += 1
+        newjob = job.Job(jobid, task, input)
+        self.jobs[jobid] = newjob
+        self._queue.put(newjob)
+        newjob.status = interfaces.QUEUED
+        return jobid
+
+    def cancel(self, jobid):
+        """See interfaces.ITaskService"""
+        for idx, job in enumerate(self._queue):
+            if job.id == jobid:
+                job.status = interfaces.CANCELLED
+                self._queue.pull(idx)
+                break
+
+    def getStatus(self, jobid):
+        """See interfaces.ITaskService"""
+        return self.jobs[jobid].status
+
+    def getResult(self, jobid):
+        """See interfaces.ITaskService"""
+        return self.jobs[jobid].output
+
+    def getError(self, jobid):
+        """See interfaces.ITaskService"""
+        return str(self.jobs[jobid].error)
+
+    def startProcessing(self):
+        """See interfaces.ITaskService"""
+        path = [parent.__name__ for parent in zapi.getParents(self)
+                 if parent.__name__]
+        path.append(self.__name__)
+        path.append('processNext')
+
+        thread = threading.Thread(
+            target=processor, args=(self._p_jar.db(), path),
+            name='remotetasks.'+self.__name__)
+        thread.running = True
+        thread.start()
+
+    def stopProcessing(self):
+        """See interfaces.ITaskService"""
+        name = 'remotetasks.'+self.__name__
+        for thread in threading.enumerate():
+            if thread.getName() == name:
+                thread.running = False
+                break
+
+    def isProcessing(self):
+        """See interfaces.ITaskService"""
+        name = 'remotetasks.' + self.__name__
+        for thread in threading.enumerate():
+            if thread.getName() == name:
+                if thread.running:
+                    return True
+        return False
+
+    def processNext(self):
+        job = self._queue.pull()
+        jobtask = zope.component.getUtility(
+            self.taskInterface, name=job.task)
+        job.started = datetime.datetime.now()
+        try:
+            job.output = jobtask(self, job.input)
+            job.status = interfaces.COMPLETED
+        except task.TaskError, error:
+            job.error = error
+            job.status = interfaces.ERROR
+        job.completed = datetime.datetime.now()
+
+    def process(self):
+        """See interfaces.ITaskService"""
+        while self._queue:
+            self.processNext()
+
+
+class ProcessorPublication(ZopePublication):
+    """A custom publication to process the next job."""
+
+    def traverseName(self, request, ob, name):
+        if hasattr(ob, '__contains__') and name in ob:
+            ob = ob[name]
+        else:
+            ob = getattr(ob, name)
+        return ob
+
+
+def processor(db, path):
+    """Job Processor
+
+    Process the jobs that are waiting in the queue. This processor is meant to
+    be run in a separate process; however, it simply goes back to the task
+    service to actually do the processing.
+    """
+    path.reverse()
+    while threading.currentThread().running:
+        request = zope.publisher.base.BaseRequest(None, {})
+        request.setPublication(ProcessorPublication(db))
+        request.setTraversalStack(path)
+        try:
+            zope.publisher.publish.publish(request, False)
+        except IndexError:
+            time.sleep(1)
+        except:
+            # This thread should never crash, thus a blank except
+            pass
\ No newline at end of file


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/service.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/task.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/task.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/task.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.interface
+from zope.schema.fieldproperty import FieldProperty
+from lovely.remotetask import interfaces
+
+class TaskError(Exception):
+    """An error occurred while executing the task."""
+    pass
+
+class SimpleTask(object):
+    """A simple, non-persistent task implementation."""
+    zope.interface.implements(interfaces.ITask)
+
+    inputSchema = FieldProperty(interfaces.ITask['inputSchema'])
+    outputSchema = FieldProperty(interfaces.ITask['outputSchema'])
+
+    def __init__(self, func):
+        self.func = func
+
+    def __call__(self, service, input):
+        return self.func(input)
+
+    def __repr__(self):
+        return '<%s %r>' %(self.__class__.__name__, self.func)
+
+
+class EchoTask(object):
+    zope.interface.implements(interfaces.ITask)
+
+    def __call__(self, service, input):
+        return input
+
+    def __repr__(self):
+        return '<%s>' %(self.__class__.__name__)


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/task.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/tests.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/tests.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/tests.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Remote Task test setup
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import doctest
+import unittest
+from zope.app.testing import placelesssetup
+from zope.testing.doctestunit import DocFileSuite
+
+def test_suite():
+    return unittest.TestSuite((
+        DocFileSuite('README.txt',
+                     setUp=placelesssetup.setUp,
+                     tearDown=placelesssetup.tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.py	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.py	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Task Service Implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.app.xmlrpcintrospection.xmlrpcintrospection import xmlrpccallable
+from zope.app.publisher.xmlrpc import XMLRPCView
+
+class RemoteTaskServiceXMLRPCAPI(XMLRPCView):
+    """An XML-RPC API for the external Remote Task Service API."""
+
+    @xmlrpccallable(list)
+    def getAvailableTasks(self):
+        """Get all available tasks that the service provides.
+
+        The result will be a list of task names.
+        """
+        return sorted(self.context.getAvailableTasks().keys())
+
+    @xmlrpccallable(int, str, dict)
+    def add(self, task, input):
+        """Add a new job to the service.
+
+        The result will be the id of the new job.
+        """
+        return self.context.add(unicode(task), input)
+
+    @xmlrpccallable(bool, int)
+    def cancel(self, jobid):
+        """Cancel a job from execution.
+
+        The result is meaningless.
+        """
+        self.context.cancel(jobid)
+        return True
+
+    @xmlrpccallable(str, int)
+    def getStatus(self, jobid):
+        """Get the status of the current job.
+
+        The return value is a string representing the status.
+        """
+        return self.context.getStatus(jobid)
+
+    @xmlrpccallable(dict, int)
+    def getResult(self, jobid):
+        """Get the result of the job execution.
+
+        The return value will be a dictionary of return values. The content of
+        the dictionary will vary with every task.
+        """
+        return self.context.getResult(jobid)
+
+    @xmlrpccallable(str, int)
+    def getError(self, jobid):
+        """Get the error from a failed job execution.
+
+        The return value will be an error message that describes the failure.
+        """
+        return self.context.getError(jobid)


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.txt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.txt	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.txt	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,419 @@
+===============================
+Remote Task Service XML-RPC API
+===============================
+
+Before we can demonstrate the XML-RPC API of the task service, we need to add
+a task service first ...
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+  >>> browser.open('http://localhost/manage')
+  >>> browser.getLink(text='Remote Task Service').click()
+  >>> browser.getControl(name='new_value').value = 'tasks'
+  >>> browser.getControl('Apply').click()
+
+... and add a few tasks to the system:
+
+  >>> import lovely.remotetask.task
+  >>> import zope.component
+
+  >>> def echo(input):
+  ...     return input
+
+  >>> echoTask = lovely.remotetask.task.SimpleTask(echo)
+  >>> zope.component.provideUtility(echoTask, name='echo')
+
+  >>> def error(input):
+  ...     raise lovely.remotetask.task.TaskError('An error occurred.')
+
+  >>> errorTask = lovely.remotetask.task.SimpleTask(error)
+  >>> zope.component.provideUtility(errorTask, name='error')
+
+
+``getAvailableTasks()`` Method
+------------------------------
+
+Get the available tasks of the service:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getAvailableTasks</methodName>
+  ... <params>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><array><data>
+  ...
+  <value><string>echo</string></value>
+  <value><string>error</string></value>
+  ...
+  </data></array></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+
+``add(task, input)`` Method
+---------------------------
+
+Add a job to the service.
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>add</methodName>
+  ... <params>
+  ... <value><string>echo</string></value>
+  ... <value><struct>
+  ... <key><string>foo</string></key>
+  ... <value><string>bar</string></value>
+  ... </struct></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><int>1</int></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+
+``cancel(jobid)`` Method
+------------------------
+
+Cancel a job.
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>cancel</methodName>
+  ... <params>
+  ... <value><int>1</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><boolean>1</boolean></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+
+``getStatus(jobid)`` Method
+---------------------------
+
+Get the status of a job.
+
+  >>> result = http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>add</methodName>
+  ... <params>
+  ... <value><string>echo</string></value>
+  ... <value><struct>
+  ... <key><string>foo</string></key>
+  ... <value><string>bar</string></value>
+  ... </struct></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getStatus</methodName>
+  ... <params>
+  ... <value><int>2</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>queued</string></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+  >>> getRootFolder()['tasks'].process()
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getStatus</methodName>
+  ... <params>
+  ... <value><int>2</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>completed</string></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+``getResult(jobid)`` Method
+---------------------------
+
+Once the job is completed we can get the result:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getResult</methodName>
+  ... <params>
+  ... <value><int>2</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><struct>
+  <member>
+  <name>foo</name>
+  <value><string>bar</string></value>
+  </member>
+  </struct></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+
+``getError(jobid)`` Method
+--------------------------
+
+When an error occured, you can get it. If no error occured, 'None' (string) is
+returned:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getError</methodName>
+  ... <params>
+  ... <value><int>2</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>None</string></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+Now let's check for real:
+
+  >>> result = http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>add</methodName>
+  ... <params>
+  ... <value><string>error</string></value>
+  ... <value><struct>
+  ... </struct></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+
+  >>> getRootFolder()['tasks'].process()
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>getError</methodName>
+  ... <params>
+  ... <value><int>3</int></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>An error occurred.</string></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+Introspection
+-------------
+
+You can get a list of all methods:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>listMethods</methodName>
+  ... <params>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><array><data>
+  <value><string>add</string></value>
+  <value><string>cancel</string></value>
+  <value><string>getAvailableTasks</string></value>
+  <value><string>getError</string></value>
+  <value><string>getResult</string></value>
+  <value><string>getStatus</string></value>
+  </data></array></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+You can also inspect each method:
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>methodHelp</methodName>
+  ... <params>
+  ... <value><string>add</string></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>Add a new job to the service.
+  <BLANKLINE>
+  The result will be the id of the new job.
+  </string></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>
+
+  >>> print http(r"""
+  ... POST /tasks/ HTTP/1.0
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: text/xml
+  ...
+  ... <?xml version='1.0'?>
+  ... <methodCall>
+  ... <methodName>methodSignature</methodName>
+  ... <params>
+  ... <value><string>add</string></value>
+  ... </params>
+  ... </methodCall>
+  ... """)
+  HTTP/1.0 200 Ok
+  ...
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><array><data>
+  <value><array><data>
+  <value><string>int</string></value>
+  <value><string>str</string></value>
+  <value><string>dict</string></value>
+  </data></array></value>
+  </data></array></value>
+  </param>
+  </params>
+  </methodResponse>
+  <BLANKLINE>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.zcml
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.zcml	2006-08-14 14:22:22 UTC (rev 69473)
+++ lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.zcml	2006-08-14 14:42:14 UTC (rev 69474)
@@ -0,0 +1,11 @@
+<configure
+    xmlns="http://namespaces.zope.org/xmlrpc">
+
+  <view
+      for=".interfaces.ITaskService"
+      methods="getAvailableTasks add cancel getStatus getResult getError"
+      class=".xmlrpc.RemoteTaskServiceXMLRPCAPI"
+      permission="zope.View"
+      />
+
+</configure>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/xmlrpc.zcml
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list