* looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-488 to compare with
* comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-488
M  MoinMoin/server/standalone.py
M  MoinMoin/server/twistedmoin.py
M  MoinMoin/_tests/test_pysupport.py
M  wiki/server/moinmodpy.py
M  MoinMoin/wikirpc.py
M  wiki/server/moin.fcg
M  MoinMoin/Page.py
M  MoinMoin/multiconfig.py
M  MoinMoin/formatter/base.py
M  MoinMoin/macro/TableOfContents.py
M  MoinMoin/parser/wiki.py
M  MoinMoin/request.py
M  MoinMoin/userform.py
M  MoinMoin/util/pysupport.py
M  MoinMoin/wikiaction.py
M  MoinMoin/wikimacro.py
M  MoinMoin/wikiutil.py
M  MoinMoin/formatter/text_python.py
M  MoinMoin/config.py

* modified files

--- orig/MoinMoin/Page.py
+++ mod/MoinMoin/Page.py
@@ -1011,8 +1011,8 @@
                     request.write(''.join(pi_formtext))
 
         # try to load the parser
-        Parser = wikiutil.importPlugin("parser", self.pi_format, "Parser",
-                                       self.cfg.data_dir)
+        Parser = wikiutil.importPlugin(self.request.cfg, "parser",
+                                       self.pi_format, "Parser")
         if Parser is None:
             # default to plain text formatter (i.e. show the page source)
             del Parser
@@ -1090,8 +1090,8 @@
             self.getFormatterName() in self.cfg.caching_formats):
             # Everything is fine, now check the parser:
             if not parser:
-                parser = wikiutil.importPlugin("parser", self.pi_format, "Parser",
-                                               self.cfg.data_dir)
+                parser = wikiutil.importPlugin(self.request.cfg, "parser",
+                                               self.pi_format, "Parser")
             return getattr(parser, 'caching', False)
 
         return False


--- orig/MoinMoin/_tests/test_pysupport.py
+++ mod/MoinMoin/_tests/test_pysupport.py
@@ -2,83 +2,107 @@
 """
     MoinMoin - MoinMoin.util.pysupport Tests
 
-    Module names must start with 'test_' to be included in the tests.
-
     @copyright: 2004 by Jürgen Hermann <ograf@bitart.de>
     @license: GNU GPL, see COPYING for details.
 """
 
-import unittest, tempfile, os
+import unittest, os
 from MoinMoin.util import pysupport
 from MoinMoin._tests import request, TestConfig
 
 
-class ImportNameTestCase(unittest.TestCase):
-    """ Test if importName works correctly
+class ImportNameFromMoinTestCase(unittest.TestCase):
+    """ Test importName of MoinMoin modules
+
+    We don't make any testing for files, assuming that moin package is
+    not broken.
     """
 
-    def setUp(self):
-        """ Create a dummy plugin path with some module inside
-        """
-        self.plugin_dir = tempfile.mktemp()
-        self.parser_dir = os.path.join(self.plugin_dir, 'plugin', 'parser')
-        os.makedirs(self.parser_dir)
-        open(os.path.join(self.plugin_dir, 'plugin', '__init__.py'), 'w')
-        open(os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.py'), 'w')
-        f = open(os.path.join(self.parser_dir, 'test.py'), 'w')
-        f.write('import sys, os\ndef test():\n    pass\n')
-        f.close()
-    
-    def tearDown(self):
-        """ Remove the plugin dir
-        """
-        for file in (os.path.join(self.plugin_dir, 'plugin', '__init__.py'),
-                     os.path.join(self.plugin_dir, 'plugin', '__init__.pyc'),
-                     os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.py'),
-                     os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.pyc'),
-                     os.path.join(self.parser_dir, 'test.py'),
-                     os.path.join(self.parser_dir, 'test.pyc')):
-            try:
-                os.unlink(file)
-            except OSError:
-                pass
-        for dir in (os.path.join(self.plugin_dir, 'plugin', 'parser'),
-                    os.path.join(self.plugin_dir, 'plugin'),
-                    self.plugin_dir):
-            os.rmdir(dir)
- 
-    def testImportName1(self):
-        """ pysupport: import nonexistant parser from moin
-        
-        Must return None."""
+    def testNonExisting(self):
+        """ pysupport: import nonexistant moin parser return None """
         self.failIf(pysupport.importName('MoinMoin.parser.abcdefghijkl',
                                          'Parser'))
 
-    def testImportName2(self):
-        """ pysupport: import wiki parser from moin
+    def testExisting(self):
+        """ pysupport: import wiki parser from moin succeed
         
         This tests if a module can be imported from the package
-        MoinMoin. Should never fail, cause importName uses
-        __import__ to do it."""
+        MoinMoin. Should never fail!
+        """
         self.failUnless(pysupport.importName('MoinMoin.parser.wiki',
                                              'Parser'))
+   
 
-    def testImportName3(self):
-        """ pysupport: import nonexistant parser plugin
+class ImportNameFromPluginTestCase(unittest.TestCase):
+    """ Test if importName form wiki plugin package """
+    
+    def setUp(self):
+        """ Check for valid plugin and parser packages """
+        # Make sure we have valid plugin and parser dir
+        self.plugindir = os.path.join(request.cfg.data_dir, 'plugin')
+        assert os.path.exists(os.path.join(self.plugindir, '__init__.py')), \
+            "Can't run tests, no plugin package"
+        self.parserdir = os.path.join(self.plugindir, 'parser')
+        assert os.path.exists(os.path.join(self.parserdir, '__init__.py')), \
+            "Can't run tests, no plugin.parser package"
+    
+    def testNonEsisting(self):
+        """ pysupport: import nonexistant plugin return None """
+        name = 'abcdefghijkl'
+
+        # Make sure that the file does not exists
+        for suffix in ['.py', '.pyc']:
+            path = os.path.join(self.parserdir, name + suffix)
+            assert not os.path.exists(path), \
+               "Can't run test, path exists: %r" % path
         
-        Must return None."""
-        self.failIf(pysupport.importName('plugin.parser.abcdefghijkl',
+        self.failIf(pysupport.importName('plugin.parser.%s' % name,
                                          'Parser'))
 
-    def testImportName4(self):
-        """ pysupport: import test parser plugin
+    def testExisting(self):
+        """ pysupport: import existing plugin succeed
         
         Tests if a module can be importet from an arbitary path
         like it is done in moin for plugins. Some strange bug
         in the old implementation failed on an import of os,
         cause os does a from os.path import that will stumple
-        over a poisoned sys.modules."""
-        self.failUnless(pysupport.importName('plugin.parser.test',
-                                             'test',
-                                             self.plugin_dir),
-                        'Failed to import the test plugin!')
+        over a poisoned sys.modules.
+        """
+        # Save a test plugin
+        pluginName = 'MoinMoinTestParser'
+        data = '''
+import sys, os
+
+class Parser:
+    pass
+'''
+        pluginPath = os.path.join(self.parserdir, pluginName + '.py')
+
+        # File must not exists - or we might destroy user data!
+        for suffix in ['.py', '.pyc']:
+            path = os.path.join(self.parserdir, pluginName + suffix)
+            assert not os.path.exists(path), \
+               "Can't run test, path exists: %r" % path
+        
+        try:
+            # Write test plugin
+            f = file(pluginPath, 'w')
+            f.write(data)
+            f.close()
+
+            modulename = request.cfg.siteid + '.plugin.parser.' + pluginName
+            name = 'Parser'
+            plugin = pysupport.importName(modulename, name)
+            self.failUnless(plugin, 'Failed to import the test plugin')
+
+            # Check that we got class
+            self.failUnless(isinstance(plugin(), plugin), \
+                            "Imported name is wrong")
+
+        finally:
+            # Remove the test plugin, including the pyc file.
+            try:
+                os.unlink(pluginPath)
+                os.unlink(pluginPath + 'c')
+            except OSError:
+                pass


--- orig/MoinMoin/config.py
+++ mod/MoinMoin/config.py
@@ -4,6 +4,10 @@
 
 import re
 
+# Threads flag - if you write a moin server that use threads, import
+# config in the server and set this flag to True.
+use_threads = False
+
 # Charset - we support only 'utf-8'. While older encodings might work,
 # we don't have the resources to test them, and there is no real
 # benefit for the user.


--- orig/MoinMoin/formatter/base.py
+++ mod/MoinMoin/formatter/base.py
@@ -242,14 +242,12 @@
             writes out the result instead of returning it!
         """
         if not is_parser:
-            processor = wikiutil.importPlugin("processor",
-                                              processor_name, "process",
-                                              self.request.cfg.data_dir)
+            processor = wikiutil.importPlugin(self.request.cfg, "processor",
+                                              processor_name, "process")
             processor(self.request, self, lines)
         else:
-            parser = wikiutil.importPlugin("parser",
-                                           processor_name, "Parser",
-                                           self.request.cfg.data_dir)
+            parser = wikiutil.importPlugin(self.request.cfg, "parser",
+                                           processor_name, "Parser")
             args = self._get_bang_args(lines[0])
             if args is not None:
                 lines=lines[1:]


--- orig/MoinMoin/formatter/text_python.py
+++ mod/MoinMoin/formatter/text_python.py
@@ -178,13 +178,11 @@
         prints out the result insted of returning it!
         """
         if not is_parser:
-            Dependencies = wikiutil.importPlugin("processor",
-                                                 processor_name, "Dependencies",
-                                                 self.request.cfg.data_dir)
+            Dependencies = wikiutil.importPlugin(self.request.cfg, "processor",
+                                                 processor_name, "Dependencies")
         else:
-            Dependencies = wikiutil.importPlugin("parser",
-                                                 processor_name, "Dependencies",
-                                                 self.request.cfg.data_dir)
+            Dependencies = wikiutil.importPlugin(self.request.cfg, "parser",
+                                                 processor_name, "Dependencies")
             
         if Dependencies == None:
             Dependencies = ["time"]


--- orig/MoinMoin/macro/TableOfContents.py
+++ mod/MoinMoin/macro/TableOfContents.py
@@ -62,7 +62,8 @@
 
     def IncludeMacro(self, *args, **kwargs):
         if self.include_macro is None:
-            self.include_macro = wikiutil.importPlugin('macro', "Include")
+            self.include_macro = wikiutil.importPlugin(self.macro.request.cfg,
+                                                       'macro', "Include")
         return self.pre_re.sub('',apply(self.include_macro, args, kwargs)).split('\n')
 
     def run(self):


--- orig/MoinMoin/multiconfig.py
+++ mod/MoinMoin/multiconfig.py
@@ -70,9 +70,9 @@
             module =  __import__(name, globals(), {})
             Config = getattr(module, 'Config', None)
             if Config:
-                # Config found, return config instance
-                cfg = Config()
-                cfg.siteid = name
+                # Config found, return config instance using name as
+                # site identifier (name must be uniqe of our url_re).
+                cfg = Config(name)
                 return cfg
             else:
                 # Broken config file, probably old config from 1.2
@@ -267,8 +267,9 @@
     url_mappings = {}
     SecurityPolicy = None
 
-    def __init__(self):
+    def __init__(self, siteid):
         """ Init Config instance """
+        self.siteid = siteid
         if self.config_check_enabled:
             self._config_check()
             
@@ -277,6 +278,9 @@
 
         # Make sure directories are accessible
         self._check_directories()
+
+        # Load plugin module
+        self._loadPluginModule()
         
         # Normalize values
         self.default_lang = self.default_lang.lower()
@@ -415,7 +419,52 @@
 also the spelling of the directory name.
 ''' % {'attr': attr, 'path': path,}
                 raise error.ConfigurationError(msg)
+
+    def _loadPluginModule(self):
+        """ import plugin module under configname.plugin """
+        import sys, imp
+
+        # First try to load the module from sys.modules - fast!
+        name = self.siteid + '.plugin'
         
+        # Get lock functions - require Python 2.3
+        try:
+            acquire_lock = imp.acquire_lock
+            release_lock = imp.release_lock
+        except AttributeError:
+            def acquire_lock(): pass
+            def release_lock(): pass
+             
+        # Lock other threads while we check and import
+        acquire_lock()
+        try:
+            try:
+                sys.modules[name]
+            except KeyError:
+                # Find module on disk and try to load - slow!
+                fp, path, info = imp.find_module('plugin', [self.data_dir])
+                try:
+                    try:
+                        # Load the module and set in sys.modules             
+                        module = imp.load_module(name, fp, path, info)
+                        sys.modules[self.siteid].plugin = module
+                    finally:
+                        # Make sure fp is closed properly
+                        if fp:
+                            fp.close()
+                except ImportError, err:
+                    msg = '''
+Could not import plugin package from "%(path)s" because of ImportError:
+%(err)s.
+
+Make sure your data directory path is correct, check permissions, and
+that the plugin directory has an __init__.py file.
+''' % {'path': self.data_dir, 'err': str(err)}
+                    raise error.ConfigurationError(msg)
+
+        finally:
+            release_lock()
+
     def __getitem__(self, item):
         """ Make it possible to access a config object like a dict """
         return getattr(self, item)


--- orig/MoinMoin/parser/wiki.py
+++ mod/MoinMoin/parser/wiki.py
@@ -273,7 +273,7 @@
         # can handle)
         base, ext = os.path.splitext(url)
         if inline:
-            Parser = wikiutil.getParserForExtension(self.cfg, ext)
+            Parser = wikiutil.getParserForExtension(self.request, ext)
             if Parser is not None:
                 content = file(fpath, 'r').read()
                 # Try to decode text. It might return junk, but we don't
@@ -811,16 +811,12 @@
         elif s_word[:2] == '#!':
             # first try to find a processor for this (will go away in 1.4)
             processor_name = s_word[2:].split()[0]
-            self.processor = wikiutil.importPlugin("processor", 
-                                                   processor_name, 
-                                                   "process", 
-                                                   self.request.cfg.data_dir)
+            self.processor = wikiutil.importPlugin(
+                self.request.cfg, "processor", processor_name, "process")
             # now look for a parser with that name
             if self.processor is None:
-                self.processor = wikiutil.importPlugin("parser",
-                                                       processor_name,
-                                                       "Parser",
-                                                       self.request.cfg.data_dir)
+                self.processor = wikiutil.importPlugin(
+                    self.request.cfg, "parser", processor_name, "Parser")
                 if self.processor:
                     self.processor_is_parser = 1
 
@@ -965,11 +961,13 @@
         if self.cfg.allow_numeric_entities:
             rules = ur'(?P<ent_numeric>&#\d{1,5};)|' + rules
 
+        self.request.clock.start('compile_huge_and_ugly')        
         scan_re = re.compile(rules, re.UNICODE)
         number_re = re.compile(self.ol_rule, re.UNICODE)
         term_re = re.compile(self.dl_rule, re.UNICODE)
         indent_re = re.compile("^\s*", re.UNICODE)
         eol_re = re.compile(r'\r?\n', re.UNICODE)
+        self.request.clock.stop('compile_huge_and_ugly')        
 
         # get text and replace TABs
         rawtext = self.raw.expandtabs()
@@ -998,16 +996,13 @@
                     processor_name = ''
                     if (line.strip()[:2] == "#!"):
                         processor_name = line.strip()[2:].split()[0]
-                        self.processor = wikiutil.importPlugin("processor",
-                                                               processor_name,
-                                                               "process",
-                                                               self.request.cfg.data_dir)
+                        self.processor = wikiutil.importPlugin(
+                            self.request.cfg, "processor", processor_name, "process")
+                                                               
                         # now look for a parser with that name
                         if self.processor is None:
-                            self.processor = wikiutil.importPlugin("parser",
-                                                                   processor_name,
-                                                                   "Parser",
-                                                                   self.request.cfg.data_dir)
+                            self.processor = wikiutil.importPlugin(
+                                self.request.cfg, "parser", processor_name, "Parser") 
                             if self.processor:
                                 self.processor_is_parser = 1
                     if self.processor:


--- orig/MoinMoin/request.py
+++ mod/MoinMoin/request.py
@@ -274,21 +274,14 @@
         @return: success code
         """
         fallback = 0
-        try:
-            Theme = wikiutil.importPlugin('theme', theme_name,
-                                          'Theme',
-                                          path=self.cfg.data_dir)
+        Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
+        if Theme is None:
+            fallback = 1
+            Theme = wikiutil.importPlugin(self.cfg, 'theme',
+                                          self.cfg.theme_default, 'Theme')
             if Theme is None:
-                fallback = 1
-                Theme = wikiutil.importPlugin('theme', self.cfg.theme_default,
-                                              'Theme',
-                                              path=self.cfg.data_dir)
-                if Theme is None:
-                    raise ImportError
-        except ImportError:
-            fallback = 2
-            from MoinMoin.theme.modern import Theme
-
+                fallback = 2
+                from MoinMoin.theme.modern import Theme
         self.theme = Theme(self)
 
         return fallback
@@ -1592,6 +1585,10 @@
             @param req: the mod_python request instance
         """
         try:
+            # flags if headers sent out contained content-type or status
+            self._have_ct = 0
+            self._have_status = 0
+
             req.add_common_vars()
             self.mpyreq = req
             # some mod_python 2.7.X has no get method for table objects,
@@ -1601,9 +1598,6 @@
             else:
                 env=req.subprocess_env
             self._setup_vars_from_std_env(env)
-            # flags if headers sent out contained content-type or status
-            self._have_ct = 0
-            self._have_status = 0
             RequestBase.__init__(self)
 
         except error.FatalError, err:


--- orig/MoinMoin/server/standalone.py
+++ mod/MoinMoin/server/standalone.py
@@ -41,6 +41,12 @@
 from MoinMoin.server import Config, switchUID
 from MoinMoin.request import RequestStandAlone
 
+# Set threads flag, so other code can use proper locking
+from MoinMoin import config
+config.use_threads = True
+del config
+
+# Server globals
 httpd = None
 config = None
 moin_requests_done = 0


--- orig/MoinMoin/server/twistedmoin.py
+++ mod/MoinMoin/server/twistedmoin.py
@@ -38,6 +38,12 @@
 from MoinMoin.request import RequestTwisted
 from MoinMoin.server import Config
 
+# Set threads flag, so other code can use proper locking
+from MoinMoin import config
+config.use_threads = True
+del config
+
+# Server globals
 config = None
 
     


--- orig/MoinMoin/userform.py
+++ mod/MoinMoin/userform.py
@@ -343,7 +343,7 @@
         """ Create theme selection. """
         cur_theme = self.request.user.valid and self.request.user.theme_name or self.cfg.theme_default
         options = []
-        for theme in wikiutil.getPlugins('theme', self.request.cfg.data_dir):
+        for theme in wikiutil.getPlugins('theme', self.request.cfg):
             options.append((theme, theme))
                 
         return util.web.makeSelection('theme_name', options, cur_theme)


--- orig/MoinMoin/util/pysupport.py
+++ mod/MoinMoin/util/pysupport.py
@@ -10,6 +10,9 @@
 ### Module import / Plugins
 #############################################################################
 
+import sys
+
+
 def isImportable(module):
     """ Check whether a certain module is available.
     """
@@ -34,67 +37,46 @@
     return modules
 
 
-def importName(modulename, name, path=None):
-    """ Import a named object from a module in the context of this function,
-        which means you should use fully qualified module paths.
+def importName(modulename, name):
+    """ Import name dynamically from module
 
-        Return None on failure.
+    Used to do dynamic import of modules and names that you know their
+    names only in runtime.
+    
+    @param modulename: full qualified mudule name, e.g. x.y.z
+    @param name: name to import from modulename
+    @rtype: any object
+    @return: name from module or None if there is no such name
     """
-    if path:
-        #
-        # see Python-src/Demo/imputil/knee.py how import should be done
-        #
-        import imp, sys
-
-        items = modulename.split('.')
-        parent = None
-        real_path = [path]
-        fqname = None
-        for part_name in items:
-            # keep full qualified module name up to date
-            if fqname is None:
-                fqname = part_name
-            else:
-                fqname = fqname + '.' + part_name
-            ## this is the place to check sys.modules if the module
-            ## is already available
-            ## WARNING: this does not work with farm wikis, cause all
-            ## farm plugin paths would map to the same 'plugin' top
-            ## module!
-            ## We need a dummy module ('wiki_'+sha(path)) to keep them
-            ## apart (create with imp.new_module()?)
-            # find & import the module
-            try:
-                fp, pathname, stuff = imp.find_module(part_name, real_path or parent.__path__)
-            except ImportError:
-                # no need to close fp here, cause its only open if no
-                # error occurs
-                return None
-            try:
-                try:
-                    mod = imp.load_module(fqname, fp, pathname, stuff)
-                except ImportError: # ValueError, if fp is no file, not possible
-                    return None
-            finally:
-                if fp: fp.close()
-            # update parent module up to date
-            if parent is None:
-                # we only need real_path for the first import, after
-                # this parent.__path__ is enough
-                real_path = None
-            else:
-                setattr(parent, part_name, mod)
-            parent = mod
-        return getattr(mod, name, None)
-    else:
-        # this part is for MoinMoin imports, we use __import__
-        # which will also use sys.modules as cache, but thats
-        # no harm cause MoinMoin is global anyway.
-        try:
-            module = __import__(modulename, globals(), {}, [name]) # {} was: locals()
-        except ImportError:
-            return None
+    try:
+        module = __import__(modulename, globals(), {}, [name])
         return getattr(module, name, None)
+    except ImportError:
+        return None
 
-# if you look for importPlugin: see wikiutil.importPlugin
 
+def makeThreadSafe(function, lock=None):
+    """ Call with a fuction you want to make thread safe
+
+    Call without lock to make the fuction thread safe using one lock per
+    function. Call with exsiting lock object if you want to make several
+    fuctions use same lock, e.g. all fuctions that change same data
+    structure.
+
+    @param fuction: function to make thread safe
+    @param lock: threading.Lock instance or None
+    @rtype: function
+    @retrun: function decoreated with locking
+    """
+    if lock is None:
+        import threading
+        lock = threading.Lock()
+    
+    def decorated(*args, **kw):
+        lock.acquire()
+        try:
+            return function(*args, **kw)
+        finally:
+            lock.release()
+            
+    return decorated


--- orig/MoinMoin/wikiaction.py
+++ mod/MoinMoin/wikiaction.py
@@ -749,9 +749,8 @@
         mimetype = u"text/plain"
 
     # try to load the formatter
-    Formatter = wikiutil.importPlugin("formatter",
-        mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter",
-        path=request.cfg.data_dir)
+    Formatter = wikiutil.importPlugin(request.cfg, "formatter",
+        mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter")
     if Formatter is None:
         # default to plain text formatter
         del Formatter
@@ -846,9 +845,12 @@
     if action in request.cfg.excluded_actions:
         return None
 
-    handler = wikiutil.importPlugin("action", action, identifier, request.cfg.data_dir)
-    if handler: return handler
+    handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
+    if handler is None:
+        handler = globals().get('do_' + action)
+        
+    return handler
 
-    return globals().get('do_' + action, None)
+    
  
 


--- orig/MoinMoin/wikimacro.py
+++ mod/MoinMoin/wikimacro.py
@@ -35,7 +35,7 @@
         return cfg.macro_names
     else:
         lnames = names[:]
-        lnames.extend(wikiutil.getPlugins('macro', cfg.data_dir))
+        lnames.extend(wikiutil.getPlugins('macro', cfg))
         return lnames
 
 def _make_index_key(index_letters, additional_html=""):
@@ -96,7 +96,7 @@
         self.cfg = self.request.cfg
 
     def execute(self, macro_name, args):
-        macro = wikiutil.importPlugin('macro', macro_name, path=self.cfg.data_dir)
+        macro = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name)
         if macro:
             return macro(self, args)
 
@@ -129,7 +129,8 @@
     def get_dependencies(self, macro_name):
         if self.Dependencies.has_key(macro_name):
             return self.Dependencies[macro_name]
-        result = wikiutil.importPlugin('macro', macro_name, 'Dependencies', self.cfg.data_dir)
+        result = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name,
+                                       'Dependencies')
         if result != None:
             return result
         else:
@@ -366,7 +367,7 @@
         row(_('Global extension macros'), 
             ', '.join(macro.extension_macros) or nonestr)
         row(_('Local extension macros'), 
-            ', '.join(wikiutil.extensionPlugins('macro', self.cfg.data_dir)) or nonestr)
+            ', '.join(wikiutil.wikiPlugins('macro', self.cfg)) or nonestr)
         ext_actions = []
         for a in action.extension_actions:
             if not a in self.request.cfg.excluded_actions:


--- orig/MoinMoin/wikirpc.py
+++ mod/MoinMoin/wikirpc.py
@@ -391,7 +391,8 @@
                 fn = getattr(self, 'xmlrpc_' + method)
                 response = fn(*params)
             except AttributeError:
-                fn = wikiutil.importPlugin('xmlrpc', method, 'execute', self.request.cfg.data_dir)
+                fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc', method,
+                                           'execute')
                 response = fn(self, *params)
         except:
             # report exception back to server


--- orig/MoinMoin/wikiutil.py
+++ mod/MoinMoin/wikiutil.py
@@ -5,11 +5,13 @@
     @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
 """
-
+    
 import os, re, difflib
+
 from MoinMoin import util, version, config
 from MoinMoin.util import pysupport
 
+
 # Exceptions
 class InvalidFileNameError(Exception):
     """ Called when we find an invalid file name """ 
@@ -533,77 +535,121 @@
 ### Plugins
 #############################################################################
 
-def importPlugin(kind, name, function="execute", path=None):
+def importPlugin(cfg, kind, name, function="execute"):
+    """ Import wiki or builtin plugin
+    
+    Returns an object from a plugin module or None if module or
+    'function' is not found.
+
+    kind may be one of 'action', 'formatter', 'macro', 'processor',
+    'parser' or any other directory that exist in MoinMoin or
+    data/plugin
+
+    Wiki plugins will always override builtin plugins. If you want
+    specific plugin, use either importWikiPlugin or importName directly.
+    
+    @param cfg: wiki config instance
+    @param kind: what kind of module we want to import
+    @param name: the name of the module
+    @param function: the function name
+    @rtype: callable
+    @return: "function" of module "name" of kind "kind", or None
     """
-    Returns an object from a plugin module or None if module or 'function' is not found
-    kind may be one of 'action', 'formatter', 'macro', 'processor', 'parser'
-    or any other directory that exist in MoinMoin or data/plugin
+    # Try to import from the wiki
+    plugin = importWikiPlugin(cfg, kind, name, function)
+    if plugin is None:
+        # Try to get the plugin from MoinMoin
+        modulename = 'MoinMoin.%s.%s' % (kind, name)
+        plugin = pysupport.importName(modulename, function)
+        
+    return plugin
+
+
+# Here we cache our plugins until we have a wiki object.
+# WARNING: do not access directly in threaded enviromnent!
+_wiki_plugins = {}
+
+def importWikiPlugin(cfg, kind, name, function):
+    """ Import plugin from the wiki data directory
     
+    We try to import only ONCE - then cache the plugin, even if we got
+    None. This way we prevent expensive import of existing plugins for
+    each call to a plugin.
+
+    @param cfg: wiki config instance
     @param kind: what kind of module we want to import
     @param name: the name of the module
     @param function: the function name
     @rtype: callable
-    @return: "function" of module "name" of kind "kind"
+    @return: "function" of module "name" of kind "kind", or None
     """
-    # First try data/plugins
-    result = None
-    if path:
-        result = pysupport.importName("plugin." + kind + "." + name, function, path)
-    if result == None:
-        # then MoinMoin
-        result = pysupport.importName("MoinMoin." + kind + "." + name, function)
-    return result
+    global _wiki_plugins
+
+    # Wiki plugins are located under 'wikiconfigname.plugin' module.
+    modulename = '%s.plugin.%s.%s' % (cfg.siteid, kind, name)
+    key = (modulename, function)
+    try:
+        # Try cache first - fast!
+        plugin = _wiki_plugins[key]
+    except KeyError:
+        # Try to import from disk and cache result - slow!
+        plugin = pysupport.importName(modulename, function)
+        _wiki_plugins[key] = plugin
+
+    return plugin
+
+# If we use threads, make this function thread safe
+if config.use_threads:
+    importWikiPlugin = pysupport.makeThreadSafe(importWikiPlugin)
 
 def builtinPlugins(kind):
-    """
-    Gets a list of modules in MoinMoin.'kind'
+    """ Gets a list of modules in MoinMoin.'kind'
     
     @param kind: what kind of modules we look for
     @rtype: list
     @return: module names
     """
-    plugins =  pysupport.importName("MoinMoin." + kind, "modules")
-    if plugins == None:
-        return []
-    else:
-        return plugins
+    modulename = "MoinMoin." + kind
+    plugins = pysupport.importName(modulename, "modules")
+    return plugins or []
 
-def extensionPlugins(kind, path):
-    """
-    Gets a list of modules in data/plugin/'kind'
+
+def wikiPlugins(kind, cfg):
+    """ Gets a list of modules in data/plugin/'kind'
     
     @param kind: what kind of modules we look for
     @rtype: list
     @return: module names
     """
-    plugins =  pysupport.importName("plugin." + kind, "modules", path=path)
-    if plugins == None:
-        return []
-    else:
-        return plugins
+    # Wiki plugins are located in wikiconfig.plugin module
+    modulename = '%s.plugin.%s' % (cfg.siteid, kind)
+    plugins = pysupport.importName(modulename, "modules")
+    return plugins or []
 
 
-def getPlugins(kind, path):
-    """
-    Gets a list of module names.
+def getPlugins(kind, cfg):
+    """ Gets a list of plugin names of kind
     
     @param kind: what kind of modules we look for
     @rtype: list
     @return: module names
     """
-    builtin_plugins = builtinPlugins(kind)
-    extension_plugins = extensionPlugins(kind, path)[:] # use a copy to not destroy the value
-    for module in builtin_plugins:
-        if module not in extension_plugins:
-            extension_plugins.append(module)
-    return extension_plugins
+    # Copy names from builtin plugins - so we dont destroy the value
+    all_plugins = builtinPlugins(kind)[:]
+    
+    # Add extension plugins without duplicates
+    for plugin in wikiPlugins(kind, cfg):
+        if plugin not in all_plugins:
+            all_plugins.append(plugin)
+
+    return all_plugins
 
 
 #############################################################################
 ### Parsers
 #############################################################################
 
-def getParserForExtension(cfg, extension):
+def getParserForExtension(request, extension):
     """
     Returns the Parser class of the parser fit to handle a file
     with the given extension. The extension should be in the same
@@ -623,8 +669,8 @@
     if not hasattr(cfg, '_EXT_TO_PARSER'):
         import types
         etp, etd = {}, None
-        for pname in getPlugins('parser', cfg.data_dir):
-            Parser = importPlugin('parser', pname, 'Parser', cfg.data_dir)
+        for pname in getPlugins('parser', cfg):
+            Parser = importPlugin(request.cfg, 'parser', pname, 'Parser')
             if Parser is not None:
                 if hasattr(Parser, 'extensions'):
                     exts=Parser.extensions
@@ -633,9 +679,11 @@
                             etp[ext]=Parser
                     elif str(exts) == '*':
                         etd=Parser
-        # is this setitem thread save?
+        # Cache in cfg for current request - this is not thread safe
+        # when cfg will be cached per wiki in long running process.
         cfg._EXT_TO_PARSER=etp
         cfg._EXT_TO_PARSER_DEFAULT=etd
+        
     return cfg._EXT_TO_PARSER.get(extension,cfg._EXT_TO_PARSER_DEFAULT)
 
 


--- orig/wiki/server/moin.fcg
+++ mod/wiki/server/moin.fcg
@@ -20,16 +20,31 @@
 ## sys.path.insert(0, '/path/to/wikiconfig/dir')
 ## sys.path.insert(0, '/path/to/farmconfig/dir')
 
+# Use threaded version or non-threaded version (default True)?
+threads = True
+
+
+# Code ------------------------------------------------------------------
+
+# Do not touch unless you know what you are doting!
+# TODO: move to server packge?
+
+# Set threads flag, so other code can use proper locking
+from MoinMoin import config
+config.use_threads = threads
+del config
 
 from MoinMoin.request import RequestFastCGI
 from MoinMoin.support import thfcgi
 
 def handle_request(req, env, form):
-    request = RequestFastCGI(req,env,form)
+    request = RequestFastCGI(req, env, form)
     request.run()
 
 if __name__ == '__main__':
-    # this is a multi-threaded FastCGI
-    # use thfcgi.unTHFCGI for a single-threaded instance
-    fcg = thfcgi.THFCGI(handle_request)
+    if threads:
+        fcg = thfcgi.THFCGI(handle_request)
+    else:
+        fcg = thfcgi.unTHFCGI(handle_request)    
+
     fcg.run()


--- orig/wiki/server/moinmodpy.py
+++ mod/wiki/server/moinmodpy.py
@@ -42,6 +42,14 @@
 ## sys.path.insert(0, '/path/to/farmconfig/dir')
 
 
+# Set threads flag, so other code can use proper locking.
+# TODO: It seems that modpy does not use threads, so we don't need to
+# set it here. Do we have another method to check this?
+from MoinMoin import config
+config.use_threads = True
+del config
+
+
 from MoinMoin.request import RequestModPy
 
 def handler(request):



