changeset 1407:e1419b1f9679

Fixed parameter checking code, now it evens gives you a warning instead of a traceback, fixed two minor typos (thanks to birkenfeld!), introduced prepare_multicall support in order to get authentication working and actually added the RPC-Aggregator.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Wed, 23 Aug 2006 21:49:35 +0200
parents ff28250a4de4
children d2fe552f1500
files MoinMoin/action/SyncPages.py MoinMoin/util/rpc_aggregator.py MoinMoin/wikisync.py
diffstat 3 files changed, 141 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/SyncPages.py	Wed Aug 23 21:20:48 2006 +0200
+++ b/MoinMoin/action/SyncPages.py	Wed Aug 23 21:49:35 2006 +0200
@@ -146,19 +146,19 @@
                 raise ActionStatus(_("The ''remoteWiki'' is unknown."))
         except ActionStatus, e:
             msg = u'<p class="error">%s</p>\n' % (e.args[0], )
-
-        try:
+        else:
             try:
-                self.sync(params, local, remote)
-            except Exception, e:
-                self.log_status(self.ERROR, _("A severe error occured:"), raw_suffix=repr(e))
-                raise
-            else:
-                msg = u"%s" % (_("Syncronisation finished. Look below for the status messages."), )
-        finally:
-            # XXX aquire readlock on self.page
-            self.page.saveText(self.page.get_raw_body() + "\n\n" + self.generate_log_table(), 0)
-            # XXX release readlock on self.page
+                try:
+                    self.sync(params, local, remote)
+                except Exception, e:
+                    self.log_status(self.ERROR, _("A severe error occured:"), raw_suffix=repr(e))
+                    raise
+                else:
+                    msg = u"%s" % (_("Syncronisation finished. Look below for the status messages."), )
+            finally:
+                # XXX aquire readlock on self.page
+                self.page.saveText(self.page.get_raw_body() + "\n\n" + self.generate_log_table(), 0)
+                # XXX release readlock on self.page
 
         self.page.send_page(self.request, msg=msg)
 
@@ -170,7 +170,7 @@
                 Wiki A    | Wiki B   | Remark
                 ----------+----------+------------------------------
                 exists    | non-     | Now the wiki knows that the page was renamed.
-                with tags | existant | There should be an RPC method that asks
+                with tags | existing | There should be an RPC method that asks
                           |          | for the new name (which could be recorded
                           |          | on page rename). Then the page is
                           |          | renamed in Wiki A as well and the sync
@@ -399,7 +399,7 @@
 
                 # XXX release lock
 
-        rpc_aggregator.scheduler(remote.create_multicall_object, handle_page, m_pages, 8)
+        rpc_aggregator.scheduler(remote.create_multicall_object, handle_page, m_pages, 8, remote.prepare_multicall)
 
 
 def execute(pagename, request):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/util/rpc_aggregator.py	Wed Aug 23 21:49:35 2006 +0200
@@ -0,0 +1,118 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - RPC Aggregator
+    
+    Aggregates RPC calls into MultiCall batches in order to increase
+    the speed.
+
+    @copyright: 2006 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import xmlrpclib
+INVALID = object()
+
+class RPCYielder(object):
+    """ If you want to have a batchable function, you need
+    to inherit from this class and define a method "run" that
+    takes exactly one argument.
+    This method has to be a generator that yields (func, arg)
+    tuples whereas func is the RPC method name (str).
+    You can fetch the calls by calling fetch_call(),
+    then you have to return the result by calling set_result(res).
+    """
+
+    def __init__(self, arg, raise_fault=False):
+        self._comm_slot = [INVALID]
+        self.raise_fault = raise_fault
+        self._gen = self.run(arg)
+
+    def fetch_call(self):
+        try:
+            next_item = self._gen.next()
+        except StopIteration:
+            return None
+        return next_item
+
+    def set_result(self, result):
+        self._comm_slot[0] = result
+
+    def fetch_result(self):
+        result = self._comm_slot[0]
+        try:
+            if result is INVALID:
+                return RuntimeError("Invalid state, there is no result to fetch.")
+            if self.raise_fault and isinstance(result, xmlrpclib.Fault):
+                raise result
+            else:
+                return result
+        finally:
+            self._comm_slot[0] = INVALID
+
+    def run(self, arg):
+        return NotImplementedError
+
+
+def scheduler(multicall_func, handler, args, max_calls=10, prepare_multicall_func=None):
+    # all generator (or better, RPCYielder) instances
+    gens = []
+    # those instances that have to be queried in the next step again
+    gens_todo = []
+    # pending calls, stored as tuples: (generator, (funcname, arg))
+    call_list = []
+
+    # instantiate generators
+    for arg in args:
+        gens.append(handler(arg))
+    # schedule generators
+    while gens:
+        for gen in gens:
+            if len(call_list) > max_calls:
+                gens_todo.append(gen)
+                continue
+            call = gen.fetch_call()
+            if call is not None:
+                call_list.append((gen, call))
+                gens_todo.append(gen)
+        if call_list:
+            if prepare_multicall_func is not None:
+                pre_calls = [(RPCYielder(0), x) for x in prepare_multicall_func()]
+                call_list = pre_calls + call_list
+
+            #print "doing multicall with %r" % call_list
+            m = multicall_func()
+            gens_result = [] # generators that will get a result
+            for gen, (func, args) in call_list:
+                gens_result.append(gen)
+                getattr(m, func)(*args) # register call
+            result = iter(m()) # execute multicall
+            for gen in gens_result:
+                try:
+                    item = result.next()
+                except xmlrpclib.Fault, e:
+                    # this exception is reraised by the RPCYielder
+                    item = e
+                gen.set_result(item)
+            call_list = []
+        gens = gens_todo
+        gens_todo = []
+
+
+def scheduler_simple(multicall_func, handler, args):
+    for arg in args:
+        cur_handler = handler(arg)
+        while 1:
+            call = cur_handler.fetch_call()
+            if call is not None:
+                func, arg = call
+                m = multicall_func()
+                getattr(m, func)(arg) # register call
+                result = iter(m()) # execute multicall
+                try:
+                    item = result.next()
+                except xmlrpclib.Fault, e:
+                    # this exception is reraised by the RPCYielder
+                    item = e
+                cur_handler.set_result(item)
+            else:
+                break
--- a/MoinMoin/wikisync.py	Wed Aug 23 21:20:48 2006 +0200
+++ b/MoinMoin/wikisync.py	Wed Aug 23 21:49:35 2006 +0200
@@ -86,7 +86,7 @@
 
     def __eq__(self, other):
         if not isinstance(other, SyncPage):
-            return false
+            return False
         return self.name == other.name
 
     def add_missing_pagename(self, local, remote):
@@ -252,6 +252,14 @@
         """ Generates an object that can be used like a MultiCall instance. """
         return MultiCall(self.connection)
 
+    def prepare_multicall(self):
+        """ Can be used to return initial calls that e.g. authenticate the user.
+            @return: [(funcname, (arg,+)*]
+        """
+        if self.token:
+            return [("applyAuthToken", (self.token, ))]
+        return []
+
     # Methods implementing the RemoteWiki interface
 
     def get_interwiki_name(self):