changeset 5071:bcd133ffa065

merged main
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Mon, 31 Aug 2009 18:59:51 +0200
parents 053353817f70 (diff) 10965bc1ee3c (current diff)
children 6cdf52df1219
files
diffstat 2212 files changed, 271953 insertions(+), 3783 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/Page.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/Page.py	Mon Aug 31 18:59:51 2009 +0200
@@ -961,14 +961,15 @@
         pi['acl'] = security.AccessControlList(request.cfg, acl)
         return pi
 
-    def send_raw(self, content_disposition=None):
+    def send_raw(self, content_disposition=None, mimetype=None):
         """ Output the raw page data (action=raw).
             With no content_disposition, the browser usually just displays the
             data on the screen, with content_disposition='attachment', it will
             offer a dialogue to save it to disk (used by Save action).
+            Supplied mimetype overrides default text/plain.
         """
         request = self.request
-        request.mimetype = 'text/plain'
+        request.mimetype = mimetype or 'text/plain'
         if self.exists():
             # use the correct last-modified value from the on-disk file
             # to ensure cacheability where supported. Because we are sending
--- a/MoinMoin/_tests/test_PageEditor.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/_tests/test_PageEditor.py	Mon Aug 31 18:59:51 2009 +0200
@@ -168,6 +168,7 @@
 
     def teardown_method(self, method):
         self.request.cfg.event_handlers = self.old_handlers
+        nuke_page(self.request, u'AutoCreatedMoinMoinTemporaryTestPageFortestSave')
 
     def testSaveAbort(self):
         """Test if saveText() is interrupted if PagePreSave event handler returns Abort"""
--- a/MoinMoin/_tests/test_wikiutil.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Mon Aug 31 18:59:51 2009 +0200
@@ -1040,4 +1040,51 @@
                 result = wikiutil.normalize_pagename(test, self.request.cfg)
                 assert result == expected
 
+class TestVersion(object):
+    def test_Version(self):
+        Version = wikiutil.Version
+        # test properties
+        assert Version(1, 2, 3).major == 1
+        assert Version(1, 2, 3).minor == 2
+        assert Version(1, 2, 3).release == 3
+        assert Version(1, 2, 3, '4.5alpha6').additional == '4.5alpha6'
+        # test Version init and Version to str conversion
+        assert str(Version(1)) == "1.0.0"
+        assert str(Version(1, 2)) == "1.2.0"
+        assert str(Version(1, 2, 3)) == "1.2.3"
+        assert str(Version(1, 2, 3, '4.5alpha6')) == "1.2.3-4.5alpha6"
+        assert str(Version(version='1.2.3')) == "1.2.3"
+        assert str(Version(version='1.2.3-4.5alpha6')) == "1.2.3-4.5alpha6"
+        # test Version comparison, trivial cases
+        assert Version() == Version()
+        assert Version(1) == Version(1)
+        assert Version(1, 2) == Version(1, 2)
+        assert Version(1, 2, 3) == Version(1, 2, 3)
+        assert Version(1, 2, 3, 'foo') == Version(1, 2, 3, 'foo')
+        assert Version(1) != Version(2)
+        assert Version(1, 2) != Version(1, 3)
+        assert Version(1, 2, 3) != Version(1, 2, 4)
+        assert Version(1, 2, 3, 'foo') != Version(1, 2, 3, 'bar')
+        assert Version(1) < Version(2)
+        assert Version(1, 2) < Version(1, 3)
+        assert Version(1, 2, 3) < Version(1, 2, 4)
+        assert Version(1, 2, 3, 'bar') < Version(1, 2, 3, 'foo')
+        assert Version(2) > Version(1)
+        assert Version(1, 3) > Version(1, 2)
+        assert Version(1, 2, 4) > Version(1, 2, 3)
+        assert Version(1, 2, 3, 'foo') > Version(1, 2, 3, 'bar')
+        # test Version comparison, more delicate cases
+        assert Version(1, 12) > Version(1, 9)
+        assert Version(1, 12) > Version(1, 1, 2)
+        assert Version(1, 0, 0, '0.0a2') > Version(1, 0, 0, '0.0a1')
+        assert Version(1, 0, 0, '0.0b1') > Version(1, 0, 0, '0.0a9')
+        assert Version(1, 0, 0, '0.0b2') > Version(1, 0, 0, '0.0b1')
+        assert Version(1, 0, 0, '0.0c1') > Version(1, 0, 0, '0.0b9')
+        assert Version(1, 0, 0, '1') > Version(1, 0, 0, '0.0c9')
+        # test Version playing nice with tuples
+        assert Version(1, 2, 3) == (1, 2, 3, '')
+        assert Version(1, 2, 4) > (1, 2, 3)
+
+
 coverage_modules = ['MoinMoin.wikiutil']
+
--- a/MoinMoin/action/AttachFile.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/action/AttachFile.py	Mon Aug 31 18:59:51 2009 +0200
@@ -89,13 +89,13 @@
             url = request.href(pagename, rename=wikiutil.taintfilename(filename),
                                action=action_name)
         else:
-            url = request.href(pagename, rename=wikiutil.taintfilename(filename),
-                               drawing=drawing, action=action_name)
+            url = request.href(pagename,
+                               target=drawing, action=request.cfg.drawing_action)
     else:
         if not drawing:
             url = request.href(pagename, target=filename, action=action_name, do=do)
         else:
-            url = request.href(pagename, drawing=drawing, action=action_name)
+            url = request.href(pagename, target=drawing, action=request.cfg.drawing_action)
     return url
 
 
@@ -395,47 +395,6 @@
         request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
                       wikiutil.escape(fname, 1), url))
 
-
-def send_hotdraw(pagename, request):
-    _ = request.getText
-
-    now = time.time()
-    pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin"
-    basename = request.values['drawing']
-    ci = ContainerItem(request, pagename, basename + '.tdraw')
-    drawpath = ci.member_url(basename + '.draw')
-    pngpath = ci.member_url(basename + '.png')
-    pagelink = request.href(pagename, action=action_name, ts=now)
-    helplink = Page(request, "HelpOnActions/AttachFile").url(request)
-    savelink = request.href(pagename, action=action_name, do='savedrawing')
-    #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
-                                           # request, {'savename': request.values['drawing']+'.draw'}
-    #savelink = '/cgi-bin/dumpform.bat'
-
-    timestamp = '&amp;ts=%s' % now
-
-    request.write('<h2>' + _("Edit drawing") + '</h2>')
-    request.write("""
-<p>
-<img src="%(pngpath)s%(timestamp)s">
-<applet code="CH.ifa.draw.twiki.TWikiDraw.class"
-        archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
-<param name="drawpath" value="%(drawpath)s">
-<param name="pngpath"  value="%(pngpath)s">
-<param name="savepath" value="%(savelink)s">
-<param name="basename" value="%(basename)s">
-<param name="viewpath" value="%(pagelink)s">
-<param name="helppath" value="%(helplink)s">
-<strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
-</applet>
-</p>""" % {
-    'pngpath': pngpath, 'timestamp': timestamp,
-    'pubpath': pubpath, 'drawpath': drawpath,
-    'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
-    'basename': wikiutil.escape(basename, 1),
-})
-
-
 def send_uploadform(pagename, request):
     """ Send the HTML code for the list of already stored attachments and
         the file upload form.
@@ -488,10 +447,6 @@
     if not writeable:
         request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
 
-    if writeable and request.values.get('drawing'):
-        send_hotdraw(pagename, request)
-
-
 #############################################################################
 ### Web interface for file upload, viewing and deletion
 #############################################################################
@@ -634,47 +589,6 @@
     def exists(self):
         return os.path.exists(self.container_filename)
 
-def _do_savedrawing(pagename, request):
-    _ = request.getText
-
-    if not request.user.may.write(pagename):
-        return _('You are not allowed to save a drawing on this page.')
-
-    file_upload = request.files.get('filepath')
-    if not file_upload:
-        # This might happen when trying to upload file names
-        # with non-ascii characters on Safari.
-        return _("No file content. Delete non ASCII characters from the file name and try again.")
-
-    filename = request.form['filename']
-    basepath, basename = os.path.split(filename)
-    basename, ext = os.path.splitext(basename)
-
-    ci = ContainerItem(request, pagename, basename + '.tdraw')
-    filecontent = file_upload.stream
-    content_length = None
-    if ext == '.draw': # TWikiDraw POSTs this first
-        _addLogEntry(request, 'ATTDRW', pagename, basename + '.tdraw')
-        ci.truncate()
-        filecontent = filecontent.read() # read file completely into memory
-        filecontent = filecontent.replace("\r", "")
-    elif ext == '.map':
-        # touch attachment directory to invalidate cache if new map is saved
-        attach_dir = getAttachDir(request, pagename)
-        os.utime(attach_dir, None)
-        filecontent = filecontent.read() # read file completely into memory
-        filecontent = filecontent.strip()
-    else:
-        #content_length = file_upload.content_length
-        # XXX gives -1 for wsgiref :( If this is fixed, we could use the file obj,
-        # without reading it into memory completely:
-        filecontent = filecontent.read()
-
-    ci.put(basename + ext, filecontent, content_length)
-
-    request.write("OK")
-
-
 def _do_del(pagename, request):
     _ = request.getText
 
@@ -689,8 +603,8 @@
     _addLogEntry(request, 'ATTDEL', pagename, filename)
 
     if request.cfg.xapian_search:
-        from MoinMoin.search.Xapian import Index
-        index = Index(request)
+        from MoinMoin.search.Xapian import XapianIndex
+        index = XapianIndex(request)
         if index.exists:
             index.remove_item(pagename, filename)
 
--- a/MoinMoin/action/__init__.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/action/__init__.py	Mon Aug 31 18:59:51 2009 +0200
@@ -27,6 +27,8 @@
     @license: GNU GPL, see COPYING for details.
 """
 
+import re
+
 from MoinMoin.util import pysupport
 from MoinMoin import config, wikiutil
 from MoinMoin.Page import Page
@@ -228,13 +230,18 @@
 
 # Builtin Actions ------------------------------------------------------------
 
+MIMETYPE_CRE = re.compile('[a-zA-Z0-9.+\-]{1,100}/[a-zA-Z0-9.+\-]{1,100}')
+
 def do_raw(pagename, request):
     """ send raw content of a page (e.g. wiki markup) """
     if not request.user.may.read(pagename):
         Page(request, pagename).send_page()
     else:
         rev = request.rev or 0
-        Page(request, pagename, rev=rev).send_raw()
+        mimetype = request.values.get('mimetype', None)
+        if mimetype and not MIMETYPE_CRE.match(mimetype):
+            mimetype = None
+        Page(request, pagename, rev=rev).send_raw(mimetype=mimetype)
 
 def do_show(pagename, request, content_only=0, count_hit=1, cacheable=1, print_mode=0, mimetype=u'text/html'):
     """ show a page, either current revision or the revision given by "rev=" value.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/anywikidraw.py	Mon Aug 31 18:59:51 2009 +0200
@@ -0,0 +1,249 @@
+# -*- coding: iso-8859-2 -*-
+"""
+    MoinMoin - anywikidraw
+
+    This action is used to call anywikidraw (http://sourceforge.net/projects/anywikidraw/)
+
+    @copyright: 2001 by Ken Sugino (sugino@mediaone.net),
+                2001-2004 by Juergen Hermann <jh@web.de>,
+                2005 MoinMoin:AlexanderSchremmer,
+                2005 DiegoOngaro at ETSZONE (diego@etszone.com),
+                2007-2008 MoinMoin:ThomasWaldmann,
+                2005-2009 MoinMoin:ReimarBauer
+    @license: GNU GPL, see COPYING for details.
+"""
+import os, re
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from MoinMoin import config, wikiutil
+from MoinMoin.action import AttachFile, do_show
+from MoinMoin.Page import Page
+
+action_name = __name__.split('.')[-1]
+
+def _write_stream(content, stream, bufsize=8192):
+    if hasattr(content, 'read'): # looks file-like
+        import shutil
+        shutil.copyfileobj(content, stream, bufsize)
+    elif isinstance(content, str):
+        stream.write(content)
+    else:
+        logging.error("unsupported content object: %r" % content)
+        raise
+
+def gedit_drawing(self, url, text, **kw):
+    # This is called for displaying a drawing image by gui editor.
+    _ = self.request.getText
+    # TODO: this 'text' argument is kind of superfluous, replace by using alt=... kw arg
+    # ToDo: make this clickable for the gui editor
+    if 'alt' not in kw or not kw['alt']:
+        kw['alt'] = text
+    # we force the title here, needed later for html>wiki converter
+    kw['title'] = "drawing:%s" % wikiutil.quoteWikinameURL(url)
+    pagename, drawing = AttachFile.absoluteName(url, self.page.page_name)
+    containername = wikiutil.taintfilename(drawing) + ".tdraw"
+    drawing_url = AttachFile.getAttachUrl(pagename, containername, self.request, drawing=drawing, upload=True)
+    ci = AttachFile.ContainerItem(self.request, pagename, containername)
+    if not ci.exists():
+        title = _('Create new drawing "%(filename)s (opens in new window)"') % {'filename': drawing}
+        img = self.icon('attachimg')  # TODO: we need a new "drawimg" in similar grey style and size
+        css = 'nonexistent'
+        return self.url(1, drawing_url, css=css, title=title) + img + self.url(0)
+    kw['src'] = ci.member_url(drawing + u'.png')
+    return self.image(**kw)
+
+def attachment_drawing(self, url, text, **kw):
+    # This is called for displaying a clickable drawing image by text_html formatter.
+    # XXX text arg is unused!
+    _ = self.request.getText
+    pagename, drawing = AttachFile.absoluteName(url, self.page.page_name)
+    containername = wikiutil.taintfilename(drawing) + ".tdraw"
+
+    drawing_url = AttachFile.getAttachUrl(pagename, containername, self.request, drawing=drawing, upload=True)
+    ci = AttachFile.ContainerItem(self.request, pagename, containername)
+    if not ci.exists():
+        title = _('Create new drawing "%(filename)s (opens in new window)"') % {'filename': drawing}
+        img = self.icon('attachimg')  # TODO: we need a new "drawimg" in similar grey style and size
+        css = 'nonexistent'
+        return self.url(1, drawing_url, css=css, title=title) + img + self.url(0)
+
+    title = _('Edit drawing %(filename)s (opens in new window)') % {'filename': self.text(drawing)}
+    kw['src'] = src = ci.member_url(drawing + u'.png')
+    kw['css'] = 'drawing'
+
+    try:
+        mapfile = ci.get(drawing + u'.map')
+        map = mapfile.read()
+        mapfile.close()
+    except (KeyError, IOError, OSError):
+        map = ''
+    if map:
+        # XXX check how that is done by josef
+        # we have a image map. inline it and add a map ref to the img tag
+        mapid = 'ImageMapOf' + drawing
+        map = map.replace('%MAPNAME%', mapid)
+        # add alt and title tags to areas
+        map = re.sub(r'href\s*=\s*"((?!%ANYWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', map)
+        map = map.replace('%ANYWIKIDRAW%"', '%s" alt="%s" title="%s"' % (drawing_url, title, title))
+        # unxml, because 4.01 concrete will not validate />
+        map = map.replace('/>', '>')
+        title = _('Clickable drawing: %(filename)s') % {'filename': self.text(drawing)}
+        if 'title' not in kw:
+            kw['title'] = title
+        if 'alt' not in kw:
+            kw['alt'] = kw['title']
+        kw['usemap'] = '#'+mapid
+        return map + self.image(**kw)
+    else:
+        if 'title' not in kw:
+            kw['title'] = title
+        if 'alt' not in kw:
+            kw['alt'] = kw['title']
+        return self.url(1, drawing_url) + self.image(**kw) + self.url(0)
+
+class AnyWikiDraw(object):
+    """ anywikidraw action """
+    def __init__(self, request, pagename, target):
+        self._ = request.getText
+        self.request = request
+        self.pagename = pagename
+        self.target = target
+
+    def render_msg(self, msg, msgtype):
+        """ Called to display some message (can also be the action form) """
+        self.request.theme.add_msg(msg, msgtype)
+        do_show(self.pagename, self.request)
+
+    def save(self):
+        _ = self._
+        pagename = self.pagename
+        request = self.request
+        target = self.target
+        if not request.user.may.write(pagename):
+            return _('You are not allowed to save a drawing on this page.')
+        file_upload = request.files.get('filepath')
+        print "file_upload: %s" % file_upload
+        if not file_upload:
+            # This might happen when trying to upload file names
+            # with non-ascii characters on Safari.
+            return _("No file content. Delete non ASCII characters from the file name and try again.")
+
+        filename = request.form['filename']
+        basepath, basename = os.path.split(filename)
+        basename, ext = os.path.splitext(basename)
+        basename = basename.replace('.svg', '')
+
+        ci = AttachFile.ContainerItem(request, pagename, basename + '.tdraw')
+        filecontent = file_upload.stream
+        content_length = None
+        if ext == '.svg': # TWikiDraw POSTs this first
+            AttachFile._addLogEntry(request, 'ATTDRW', pagename, basename + '.tdraw')
+            ci.truncate()
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.replace("\r", "")
+        elif ext == '.map':
+            # touch attachment directory to invalidate cache if new map is saved
+            attach_dir = AttachFile.getAttachDir(request, pagename)
+            os.utime(attach_dir, None)
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.strip()
+        else:
+            #content_length = file_upload.content_length
+            # XXX gives -1 for wsgiref :( If this is fixed, we could use the file obj,
+            # without reading it into memory completely:
+            filecontent = filecontent.read()
+
+        if filecontent:
+            ci.put(basename + ext, filecontent, content_length)
+
+    def render(self):
+        _ = self._
+        request = self.request
+        pagename = self.pagename
+        target = self.target
+        url = request.getQualifiedURL()
+        target_url = ''
+        drawing_name = ''
+
+        htdocs = "%s%s" % (request.cfg.url_prefix_static, '/applets/anywikidraw')
+        ci = AttachFile.ContainerItem(request, pagename, target + '.tdraw')
+        if ci.exists():
+            drawing_name = "%s.svg" % target
+            target_url = ci.member_url(target + '.svg')
+
+        html = """
+    <h2> %(editdrawing)s </h2>
+<!--
+    Document   : anywikidraw_for_twiki_demo
+    Created on : May 13, 2008, 7:32:27 AM
+    Author     : werni
+    Version    : $Id: anywikidraw_for_twiki_demo.html 107 2009-06-15 19:33:05Z rawcoder $
+
+    adjusted for MoinMoin 2009-08-29 ReimarBauer
+-->
+        <applet codebase="."
+                archive="%(htdocs)s/lib/AnyWikiDrawForTWiki.jar"
+                code=org.anywikidraw.twiki.TWikiDrawingApplet.class
+                width="800" height="600">
+
+           <!-- The following parameters are used to tell AnyWikiDraw how to communicate with MoinMoin.
+           -->
+           <param name="DrawingName" value="%(target)s.svg"/>
+           <param name="DrawingWidth" value="800"/>
+           <param name="DrawingHeight" value="600"/>
+           <param name="DrawingURL" value="%(target_url)s"/>
+           <param name="PageURL" value="%(url)s/%(pagename)s/">
+           <param name="UploadURL" value="%(pagename)s?action=anywikidraw&do=save&target=%(target)s.svg"/>
+
+           <!-- The following parameters are used to configure the drawing applet -->
+           <param name="Locale" value="en"/>
+
+           <!-- The following parameters are used to configure Sun's Java Plug-In -->
+           <param name="codebase_lookup" value="false"/>
+           <param name="classloader_cache" value="false"/>
+           <param name="java_arguments" value="-Djnlp.packEnabled=true"/>
+           <param name="image" value="lib/Splash.gif"/>
+           <param name="boxborder" value="false"/>
+           <param name="centerimage" value="true"/>
+        </applet>
+""" % {'htdocs': htdocs,
+       'pagename': pagename,
+       'target': target,
+       'drawing_name': drawing_name,
+       'target_url': target_url,
+       'url': url,
+       'editdrawing': _('Edit drawing'),
+       }
+
+        title = '%s:%s' % (pagename, target)
+        request.theme.send_title(title, page=request.page, pagename=pagename)
+        request.write(request.formatter.startContent("content"))
+        request.write(request.formatter.rawHTML(html))
+        request.write(request.formatter.endContent())
+        request.theme.send_footer(pagename)
+        request.theme.send_closing_html()
+
+def execute(pagename, request):
+    _ = request.getText
+    msg = None
+    if not request.user.may.read(pagename):
+        msg = '<p>%s</p>' % _('You are not allowed to view this page.')
+        AnyWikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    target = request.values.get('target', '')
+    if not target:
+        msg = '<p>%s</p>' % _("Empty target given.")
+        AnyWikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    do = request.values.get('do', '')
+    if do == 'save' and request.user.may.write(pagename):
+        msg = AnyWikiDraw(request, pagename, target).save()
+        AnyWikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    AnyWikiDraw(request, pagename, target).render()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/twikidraw.py	Mon Aug 31 18:59:51 2009 +0200
@@ -0,0 +1,235 @@
+# -*- coding: iso-8859-2 -*-
+"""
+    MoinMoin - twikidraw
+
+    This action is used to call twikidraw
+
+    @copyright: 2001 by Ken Sugino (sugino@mediaone.net),
+                2001-2004 by Juergen Hermann <jh@web.de>,
+                2005 MoinMoin:AlexanderSchremmer,
+                2005 DiegoOngaro at ETSZONE (diego@etszone.com),
+                2007-2008 MoinMoin:ThomasWaldmann,
+                2005-2009 MoinMoin:ReimarBauer,
+    @license: GNU GPL, see COPYING for details.
+"""
+import os, re, time
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from MoinMoin import wikiutil
+from MoinMoin.action import AttachFile, do_show
+from MoinMoin.Page import Page
+
+action_name = __name__.split('.')[-1]
+
+
+def _write_stream(content, stream, bufsize=8192):
+    if hasattr(content, 'read'): # looks file-like
+        import shutil
+        shutil.copyfileobj(content, stream, bufsize)
+    elif isinstance(content, str):
+        stream.write(content)
+    else:
+        logging.error("unsupported content object: %r" % content)
+        raise
+
+def gedit_drawing(self, url, text, **kw):
+    # This is called for displaying a drawing image by gui editor.
+    _ = self.request.getText
+    # TODO: this 'text' argument is kind of superfluous, replace by using alt=... kw arg
+    # ToDo: make this clickable for the gui editor
+    if 'alt' not in kw or not kw['alt']:
+        kw['alt'] = text
+    # we force the title here, needed later for html>wiki converter
+    kw['title'] = "drawing:%s" % wikiutil.quoteWikinameURL(url)
+    pagename, drawing = AttachFile.absoluteName(url, self.page.page_name)
+    containername = wikiutil.taintfilename(drawing) + ".tdraw"
+    drawing_url = AttachFile.getAttachUrl(pagename, containername, self.request, drawing=drawing, upload=True)
+    ci = AttachFile.ContainerItem(self.request, pagename, containername)
+    if not ci.exists():
+        title = _('Create new drawing "%(filename)s (opens in new window)"') % {'filename': drawing}
+        img = self.icon('attachimg')  # TODO: we need a new "drawimg" in similar grey style and size
+        css = 'nonexistent'
+        return self.url(1, drawing_url, css=css, title=title) + img + self.url(0)
+    kw['src'] = ci.member_url(drawing + u'.png')
+    return self.image(**kw)
+
+def attachment_drawing(self, url, text, **kw):
+    # This is called for displaying a clickable drawing image by text_html formatter.
+    # XXX text arg is unused!
+    _ = self.request.getText
+    pagename, drawing = AttachFile.absoluteName(url, self.page.page_name)
+    containername = wikiutil.taintfilename(drawing) + ".tdraw"
+
+    drawing_url = AttachFile.getAttachUrl(pagename, containername, self.request, drawing=drawing, upload=True)
+    ci = AttachFile.ContainerItem(self.request, pagename, containername)
+    if not ci.exists():
+        title = _('Create new drawing "%(filename)s (opens in new window)"') % {'filename': drawing}
+        img = self.icon('attachimg')  # TODO: we need a new "drawimg" in similar grey style and size
+        css = 'nonexistent'
+        return self.url(1, drawing_url, css=css, title=title) + img + self.url(0)
+
+    title = _('Edit drawing %(filename)s (opens in new window)') % {'filename': self.text(drawing)}
+    kw['src'] = src = ci.member_url(drawing + u'.png')
+    kw['css'] = 'drawing'
+
+    try:
+        mapfile = ci.get(drawing + u'.map')
+        map = mapfile.read()
+        mapfile.close()
+    except (KeyError, IOError, OSError):
+        map = ''
+    if map:
+        # we have a image map. inline it and add a map ref to the img tag
+        mapid = 'ImageMapOf' + drawing
+        map = map.replace('%MAPNAME%', mapid)
+        # add alt and title tags to areas
+        map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', map)
+        map = map.replace('%TWIKIDRAW%"', '%s" alt="%s" title="%s"' % (drawing_url, title, title))
+        # unxml, because 4.01 concrete will not validate />
+        map = map.replace('/>', '>')
+        title = _('Clickable drawing: %(filename)s') % {'filename': self.text(drawing)}
+        if 'title' not in kw:
+            kw['title'] = title
+        if 'alt' not in kw:
+            kw['alt'] = kw['title']
+        kw['usemap'] = '#'+mapid
+        return map + self.image(**kw)
+    else:
+        if 'title' not in kw:
+            kw['title'] = title
+        if 'alt' not in kw:
+            kw['alt'] = kw['title']
+        return self.url(1, drawing_url) + self.image(**kw) + self.url(0)
+
+
+class TwikiDraw(object):
+    """ twikidraw action """
+    def __init__(self, request, pagename, target):
+        self._ = request.getText
+        self.request = request
+        self.pagename = pagename
+        self.target = target
+
+    def render_msg(self, msg, msgtype):
+        """ Called to display some message (can also be the action form) """
+        self.request.theme.add_msg(msg, msgtype)
+        do_show(self.pagename, self.request)
+
+    def save(self):
+        _ = self._
+        pagename = self.pagename
+        request = self.request
+        if not request.user.may.write(pagename):
+            return _('You are not allowed to save a drawing on this page.')
+
+        file_upload = request.files.get('filepath')
+        if not file_upload:
+            # This might happen when trying to upload file names
+            # with non-ascii characters on Safari.
+            return _("No file content. Delete non ASCII characters from the file name and try again.")
+
+        filename = request.form['filename']
+        basepath, basename = os.path.split(filename)
+        basename, ext = os.path.splitext(basename)
+        ci = AttachFile.ContainerItem(request, pagename, basename + '.tdraw')
+        filecontent = file_upload.stream
+        content_length = None
+        if ext == '.draw': # TWikiDraw POSTs this first
+            AttachFile._addLogEntry(request, 'ATTDRW', pagename, basename + '.tdraw')
+            ci.truncate()
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.replace("\r", "")
+        elif ext == '.map':
+            # touch attachment directory to invalidate cache if new map is saved
+            attach_dir = AttachFile.getAttachDir(request, pagename)
+            os.utime(attach_dir, None)
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.strip()
+        else:
+            #content_length = file_upload.content_length
+            # XXX gives -1 for wsgiref :( If this is fixed, we could use the file obj,
+            # without reading it into memory completely:
+            filecontent = filecontent.read()
+
+        ci.put(basename + ext, filecontent, content_length)
+        #return _("Thank you for your changes. Your attention to detail is appreciated.")
+
+
+    def render(self):
+        _ = self._
+        request = self.request
+        pagename = self.pagename
+        target = self.target
+        url = request.getQualifiedURL()
+        now = time.time()
+        htdocs = "%s%s" % (request.cfg.url_prefix_static, "/applets/TWikiDrawPlugin")
+        ci = AttachFile.ContainerItem(request, pagename, target + '.tdraw')
+        drawpath = ci.member_url(target + '.draw')
+        pngpath = ci.member_url(target + '.png')
+        pagelink = request.href(pagename, action=action_name, ts=now)
+        helplink = Page(request, "HelpOnActions/AttachFile").url(request)
+        savelink = request.href(pagename, action=action_name, do='save', target=target)
+        timestamp = '&amp;ts=%s' % now
+
+        html = '''<h2> %(editdrawing)s </h2>
+<p>
+<img src="%(pngpath)s%(timestamp)s">
+<applet code="CH.ifa.draw.twiki.TWikiDraw.class"
+        archive="%(htdocs)s/twikidraw.jar" width="640" height="480">
+<param name="drawpath" value="%(drawpath)s">
+<param name="pngpath"  value="%(pngpath)s">
+<param name="savepath" value="%(savelink)s">
+<param name="basename" value="%(basename)s">
+<param name="viewpath" value="%(url)s/%(pagename)s/">
+<param name="helppath" value="%(helplink)s">
+<strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
+</applet>
+</p>''' % {
+    'pagename': pagename,
+    'url': url,
+    'target': target,
+    'pngpath': pngpath,
+    'timestamp': timestamp,
+    'htdocs': htdocs,
+    'drawpath': drawpath,
+    'pagelink': pagelink,
+    'helplink': helplink,
+    'savelink': savelink,
+    'basename': wikiutil.escape(target, 1),
+    'editdrawing': _("Edit drawing")
+    }
+
+        title = "%s:%s" % (pagename, target)
+        request.theme.send_title(title, page=request.page, pagename=pagename)
+        request.write(request.formatter.startContent("content"))
+        request.write(request.formatter.rawHTML(html))
+        request.write(request.formatter.endContent())
+        request.theme.send_footer(pagename)
+        request.theme.send_closing_html()
+
+
+def execute(pagename, request):
+    _ = request.getText
+    msg = None
+    if not request.user.may.read(pagename):
+        msg = '<p>%s</p>' % _('You are not allowed to view this page.')
+        TwikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    target = request.values.get('target', '')
+    if not target:
+        msg = '<p>%s</p>' % _("Empty target given.")
+        TwikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    do = request.values.get('do', '')
+    if do == 'save' and request.user.may.write(pagename):
+        msg = TwikiDraw(request, pagename, target).save()
+        TwikiDraw(request, pagename, target).render_msg(msg, 'error')
+        return
+
+    TwikiDraw(request, pagename, target).render()
+
+
--- a/MoinMoin/caching.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/caching.py	Mon Aug 31 18:59:51 2009 +0200
@@ -3,7 +3,7 @@
     MoinMoin caching module
 
     @copyright: 2001-2004 by Juergen Hermann <jh@web.de>,
-                2006-2008 MoinMoin:ThomasWaldmann,
+                2006-2009 MoinMoin:ThomasWaldmann,
                 2008 MoinMoin:ThomasPfaff
     @license: GNU GPL, see COPYING for details.
 """
@@ -20,7 +20,7 @@
 
 
 class CacheError(Exception):
-    """ raised if we have trouble reading or writing to the cache """
+    """ raised if we have trouble locking, reading or writing """
     pass
 
 
@@ -32,6 +32,9 @@
         return os.path.join(request.cfg.cache_dir, request.cfg.siteid, arena)
     elif scope == 'farm':
         return os.path.join(request.cfg.cache_dir, '__common__', arena)
+    elif scope == 'dir':
+        # arena is a specific directory, just use it
+        return arena
     return None
 
 
@@ -55,6 +58,7 @@
                           'item' - an item local cache
                           'wiki' - a wiki local cache
                           'farm' - a cache for the whole farm
+                          'dir' - just use some specific directory
             @param do_locking: if there should be a lock, normally True
             @param use_pickle: if data should be pickled/unpickled (nice for arbitrary cache content)
             @param use_encode: if data should be encoded/decoded (nice for readable cache files)
@@ -69,13 +73,8 @@
             os.makedirs(self.arena_dir)
         self._fname = os.path.join(self.arena_dir, key)
 
-        if self.locking:
-            self.lock_dir = os.path.join(self.arena_dir, '__lock__')
-            self.rlock = lock.LazyReadLock(self.lock_dir, 60.0)
-            self.wlock = lock.LazyWriteLock(self.lock_dir, 60.0)
-
         # used by file-like api:
-        self._lock = None  # either self.rlock or self.wlock
+        self._lock = None  # either a read or a write lock
         self._fileobj = None  # open cache file object
         self._tmp_fname = None  # name of temporary file (used for write)
         self._mode = None  # mode of open file object
@@ -132,22 +131,47 @@
 
         return needsupdate
 
-    def _determine_locktype(self, mode):
-        """ return the correct lock object for a specific file access mode """
-        if self.locking:
-            if 'r' in mode:
-                lock = self.rlock
-            if 'w' in mode or 'a' in mode:
-                lock = self.wlock
-        else:
-            lock = None
-        return lock
+    def lock(self, mode, timeout=1.0):
+        """
+        acquire a lock for <mode> ("r" or "w").
+        we just raise a CacheError if this doesn't work.
+
+        Note:
+         * .open() calls .lock(), .close() calls .unlock() if do_locking is True.
+         * if you need to do a read-modify-write, you want to use a CacheEntry
+           with do_locking=False and manually call .lock('w') and .unlock().
+        """
+        lock_dir = os.path.join(self.arena_dir, '__lock__')
+        if 'r' in mode:
+            self._lock = lock.LazyReadLock(lock_dir, 60.0)
+        elif 'w' in mode:
+            self._lock = lock.LazyWriteLock(lock_dir, 60.0)
+        acquired = self._lock.acquire(timeout)
+        if not acquired:
+            err = "Can't acquire %s lock in %s" % (mode, lock_dir)
+            logging.error(err)
+            raise CacheError(err)
+
+    def unlock(self):
+        """
+        release the lock.
+        """
+        if self._lock:
+            self._lock.release()
+            self._lock = None
 
     # file-like interface ----------------------------------------------------
 
     def open(self, filename=None, mode='r', bufsize=-1):
         """ open the cache for reading/writing
 
+        Typical usage:
+            try:
+                cache.open('r')  # open file, create locks
+                data = cache.read()
+            finally:
+                cache.close()  # important to close file and remove locks
+
         @param filename: must be None (default - automatically determine filename)
         @param mode: 'r' (read, default), 'w' (write)
                      Note: if mode does not include 'b' (binary), it will be
@@ -158,32 +182,32 @@
         """
         assert self._fileobj is None, 'caching: trying to open an already opened cache'
         assert filename is None, 'caching: giving a filename is not supported (yet?)'
-
-        self._lock = self._determine_locktype(mode)
+        assert 'r' in mode or 'w' in mode, 'caching: mode must contain "r" or "w"'
 
         if 'b' not in mode:
             mode += 'b'  # we want to use binary mode, ever!
         self._mode = mode  # for self.close()
 
-        if not self.locking or self.locking and self._lock.acquire(1.0):
-            try:
-                if 'r' in mode:
-                    self._fileobj = open(self._fname, mode, bufsize)
-                elif 'w' in mode:
-                    # we do not write content to old inode, but to a new file
-                    # so we don't need to lock when we just want to read the file
-                    # (at least on POSIX, this works)
-                    fd, self._tmp_fname = tempfile.mkstemp('.tmp', self.key, self.arena_dir)
-                    self._fileobj = os.fdopen(fd, mode, bufsize)
-                else:
-                    raise ValueError("caching: mode does not contain 'r' or 'w'")
-            finally:
-                if self.locking:
-                    self._lock.release()
-                    self._lock = None
-        else:
-            logging.error("Can't acquire read/write lock in %s" % self.lock_dir)
-
+        if self.locking:
+            self.lock(mode)
+        try:
+            if 'r' in mode:
+                filename = self._fname
+                self._fileobj = open(filename, mode, bufsize)
+            elif 'w' in mode:
+                # we do not write content to old inode, but to a new file
+                # so we don't need to lock when we just want to read the file
+                # (at least on POSIX, this works)
+                filename = None
+                fd, filename = tempfile.mkstemp('.tmp', self.key, self.arena_dir)
+                self._tmp_fname = filename
+                self._fileobj = os.fdopen(fd, mode, bufsize)
+        except IOError, err:
+            if 'w' in mode:
+                # IOerror for 'r' can be just a non-existing file, do not log that,
+                # but if open fails for 'w', we likely have some bigger problem:
+                logging.error(str(err))
+            raise CacheError(str(err))
 
     def read(self, size=-1):
         """ read data from cache file
@@ -202,18 +226,17 @@
 
     def close(self):
         """ close cache file (and release lock, if any) """
-        if self._fileobj:
-            self._fileobj.close()
-            self._fileobj = None
-            if 'w' in self._mode:
-                filesys.chmod(self._tmp_fname, 0666 & config.umask) # fix mode that mkstemp chose
-                # this is either atomic or happening with real locks set:
-                filesys.rename(self._tmp_fname, self._fname)
-
-        if self._lock:
+        try:
+            if self._fileobj:
+                self._fileobj.close()
+                self._fileobj = None
+                if 'w' in self._mode:
+                    filesys.chmod(self._tmp_fname, 0666 & config.umask) # fix mode that mkstemp chose
+                    # this is either atomic or happening with real locks set:
+                    filesys.rename(self._tmp_fname, self._fname)
+        finally:
             if self.locking:
-                self._lock.release()
-            self._lock = None
+                self.unlock()
 
     # ------------------------------------------------------------------------
 
@@ -259,16 +282,15 @@
             raise CacheError(str(err))
 
     def remove(self):
-        if not self.locking or self.locking and self.wlock.acquire(1.0):
+        if self.locking:
+            self.lock('w')
+        try:
             try:
-                try:
-                    os.remove(self._fname)
-                except OSError:
-                    pass
-            finally:
-                if self.locking:
-                    self.wlock.release()
-        else:
-            logging.error("Can't acquire write lock in %s" % self.lock_dir)
+                os.remove(self._fname)
+            except OSError:
+                pass
+        finally:
+            if self.locking:
+                self.unlock()
 
 
--- a/MoinMoin/config/multiconfig.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/config/multiconfig.py	Mon Aug 31 18:59:51 2009 +0200
@@ -840,6 +840,7 @@
     ('changed_time_fmt', '%H:%M', "Time format used on Recent``Changes for page edits within the last 24 hours"),
     ('date_fmt', '%Y-%m-%d', "System date format, used mostly in Recent``Changes"),
     ('datetime_fmt', '%Y-%m-%d %H:%M:%S', 'Default format for dates and times (when the user has no preferences or chose the "default" date format)'),
+    ('drawing_action', 'twikidraw', "action for manipulating drawings"),
     ('chart_options', None, "If you have gdchart, use something like chart_options = {'width': 720, 'height': 540}"),
 
     ('edit_bar', ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu'],
--- a/MoinMoin/events/xapian_index.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/events/xapian_index.py	Mon Aug 31 18:59:51 2009 +0200
@@ -7,15 +7,21 @@
 """
 import MoinMoin.events as ev
 
+def _get_index(request):
+    try:
+        from MoinMoin.search.Xapian import XapianIndex
+        return XapianIndex(request)
+    except ImportError:
+        pass
+
 def handle_renamed(event):
     """Updates Xapian index when a page changes its name"""
 
     request = event.request
 
     if request.cfg.xapian_search:
-        from MoinMoin.search.Xapian import Index
-        index = Index(request)
-        if index.exists():
+        index = _get_index(request)
+        if index and index.exists():
             index.remove_item(event.old_page.page_name, now=0)
             index.update_page(event.page.page_name)
 
@@ -26,9 +32,8 @@
     request = event.request
 
     if request.cfg.xapian_search:
-        from MoinMoin.search.Xapian import Index
-        index = Index(request)
-        if index.exists():
+        index = _get_index(request)
+        if index and index.exists():
             index.update_page(event.page.page_name)
 
 def handle_changed(event, deleted=False):
@@ -37,9 +42,8 @@
     request = event.request
 
     if request.cfg.xapian_search:
-        from MoinMoin.search.Xapian import Index
-        index = Index(request)
-        if index.exists():
+        index = _get_index(request)
+        if index and index.exists():
             if deleted:
                 index.remove_item(event.page.page_name)
             else:
@@ -58,9 +62,8 @@
     request = event.request
 
     if request.cfg.xapian_search:
-        from MoinMoin.search.Xapian import Index
-        index = Index(request)
-        if index.exists():
+        index = _get_index(request)
+        if index and index.exists():
             index.update_page(event.pagename)
 
 
--- a/MoinMoin/filter/application_vnd_oasis_opendocument.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/filter/application_vnd_oasis_opendocument.py	Mon Aug 31 18:59:51 2009 +0200
@@ -21,8 +21,8 @@
         data = zf.read("content.xml")
         zf.close()
         data = " ".join(rx_stripxml.sub(" ", data).split())
-    except RuntimeError, err:
-        logging.error(str(err))
+    except (zipfile.BadZipfile, RuntimeError), err:
+        logging.error("%s [%s]" % (str(err), filename))
         data = ""
     return data.decode('utf-8')
 
--- a/MoinMoin/filter/application_vnd_sun_xml.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/filter/application_vnd_sun_xml.py	Mon Aug 31 18:59:51 2009 +0200
@@ -21,8 +21,8 @@
         data = zf.read("content.xml")
         zf.close()
         data = " ".join(rx_stripxml.sub(" ", data).split())
-    except RuntimeError, err:
-        logging.error(str(err))
+    except (zipfile.BadZipfile, RuntimeError), err:
+        logging.error("%s [%s]" % (str(err), filename))
         data = ""
     return data.decode('utf-8')
 
--- a/MoinMoin/formatter/text_gedit.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/formatter/text_gedit.py	Mon Aug 31 18:59:51 2009 +0200
@@ -93,19 +93,13 @@
         return self.image(**kw)
 
     def attachment_drawing(self, url, text, **kw):
-        _ = self.request.getText
-        # TODO: this 'text' argument is kind of superfluous, replace by using alt=... kw arg
-        if 'alt' not in kw or not kw['alt']:
-            kw['alt'] = text
-        # we force the title here, needed later for html>wiki converter
-        kw['title'] = "drawing:%s" % wikiutil.quoteWikinameURL(url)
-        pagename = self.page.page_name
-        if '/' in url:
-            pagename, target = AttachFile.absoluteName(url, pagename)
-            url = url.split('/')[-1]
-        url += '.png'
-        kw['src'] = AttachFile.getAttachUrl(pagename, url, self.request, addts=1)
-        return self.image(**kw)
+        # Todo get it to start the drawing editor on a click
+        try:
+            attachment_drawing = wikiutil.importPlugin(self.request.cfg, 'action',
+                                              self.request.cfg.drawing_action, 'gedit_drawing')
+            return attachment_drawing(self, url, text, **kw)
+        except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError):
+            return url
 
     def icon(self, type):
         return self.request.theme.make_icon(type, title='smiley:%s' % type)
--- a/MoinMoin/formatter/text_html.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/formatter/text_html.py	Mon Aug 31 18:59:51 2009 +0200
@@ -653,52 +653,13 @@
             return self.url(1, target, css=css, title=title) + img + self.url(0)
 
     def attachment_drawing(self, url, text, **kw):
-        # XXX text arg is unused!
-        _ = self.request.getText
-        pagename, drawing = AttachFile.absoluteName(url, self.page.page_name)
-        containername = wikiutil.taintfilename(drawing) + ".tdraw"
-
-        drawing_url = AttachFile.getAttachUrl(pagename, containername, self.request, drawing=drawing, upload=True)
-        ci = AttachFile.ContainerItem(self.request, pagename, containername)
-        if not ci.exists():
-            title = _('Create new drawing "%(filename)s (opens in new window)"') % {'filename': drawing}
-            img = self.icon('attachimg')  # TODO: we need a new "drawimg" in similar grey style and size
-            css = 'nonexistent'
-            return self.url(1, drawing_url, css=css, title=title) + img + self.url(0)
-
-        title = _('Edit drawing %(filename)s (opens in new window)') % {'filename': self.text(drawing)}
-        kw['src'] = src = ci.member_url(drawing + u'.png')
-        kw['css'] = 'drawing'
-
+        # ToDo try to move this to a better place e.g. __init__
         try:
-            mapfile = ci.get(drawing + u'.map')
-            map = mapfile.read()
-            mapfile.close()
-        except (KeyError, IOError, OSError):
-            map = ''
-        if map:
-            # we have a image map. inline it and add a map ref to the img tag
-            mapid = 'ImageMapOf' + drawing
-            map = map.replace('%MAPNAME%', mapid)
-            # add alt and title tags to areas
-            map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', map)
-            map = map.replace('%TWIKIDRAW%"', '%s" alt="%s" title="%s"' % (drawing_url, title, title))
-            # unxml, because 4.01 concrete will not validate />
-            map = map.replace('/>', '>')
-            title = _('Clickable drawing: %(filename)s') % {'filename': self.text(drawing)}
-            if 'title' not in kw:
-                kw['title'] = title
-            if 'alt' not in kw:
-                kw['alt'] = kw['title']
-            kw['usemap'] = '#'+mapid
-            return map + self.image(**kw)
-        else:
-            if 'title' not in kw:
-                kw['title'] = title
-            if 'alt' not in kw:
-                kw['alt'] = kw['title']
-            return self.url(1, drawing_url) + self.image(**kw) + self.url(0)
-
+            attachment_drawing = wikiutil.importPlugin(self.request.cfg, 'action',
+                                              self.request.cfg.drawing_action, 'attachment_drawing')
+            return attachment_drawing(self, url, text, **kw)
+        except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError):
+            return url
 
     # Text ##############################################################
 
--- a/MoinMoin/macro/SystemInfo.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/macro/SystemInfo.py	Mon Aug 31 18:59:51 2009 +0200
@@ -127,7 +127,7 @@
         row(_('Local extension parsers'),
             ', '.join(wikiutil.wikiPlugins('parser', self.macro.cfg)) or nonestr)
 
-        from MoinMoin.search.builtin import Search
+        from MoinMoin.search.builtin import XapianSearch
         xapState = (_('Disabled'), _('Enabled'))
         idxState = (_('index available'), _('index unavailable'))
 
@@ -142,7 +142,7 @@
         xapRow += ', %s' % xapVersion
 
         if xapian and xapian_enabled:
-            idx = Search._xapianIndex(request)
+            idx = XapianSearch._xapianIndex(request)
             available = idx and idxState[0] or idxState[1]
             mtime = _('last modified: %s') % (idx and
                 request.user.getFormattedDateTime(idx.mtime()) or _('N/A'))
--- a/MoinMoin/macro/_tests/test_GetVal.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/macro/_tests/test_GetVal.py	Mon Aug 31 18:59:51 2009 +0200
@@ -47,9 +47,9 @@
     def testGetValACLs(self):
         """ macro GetVal test: 'cant read VAR on an ACL protected page' """
         py.test.skip("user has no rights to create acl pages")
-        self.page = create_page(self.request, self.pagename,
-                                '#acl SomeUser:read,write All:delete\n VAR:: This is an example')
-        result = self._test_macro(u'GetVal', "%s,%s" % (self.pagename, u'VAR'))
+        page = create_page(self.request, self.pagename,
+                           '#acl SomeUser:read,write All:delete\n VAR:: This is an example')
+        result = self._test_macro(u'GetVal', "%s,%s" % (self.pagename, u'VAR'), page)
         assert result == "&lt;&lt;GetVal: You don't have enough rights on this page&gt;&gt;"
 
 coverage_modules = ['MoinMoin.macro.GetVal']
--- a/MoinMoin/script/index/build.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/script/index/build.py	Mon Aug 31 18:59:51 2009 +0200
@@ -59,6 +59,6 @@
     """ Xapian index build script class """
 
     def command(self):
-        from MoinMoin.search.Xapian import Index
-        Index(self.request).indexPages(self.files, self.options.mode)
+        from MoinMoin.search.Xapian import XapianIndex
+        XapianIndex(self.request).indexPages(self.files, self.options.mode)
 
--- a/MoinMoin/script/migration/_tests/test_conv160_wiki.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/script/migration/_tests/test_conv160_wiki.py	Mon Aug 31 18:59:51 2009 +0200
@@ -21,7 +21,11 @@
 import py
 #py.test.skip("broken")
 
+from MoinMoin import i18n
+i18n_wikiLanguages = i18n.wikiLanguages
+# convert_wiki overwrites i18n.wikiLanguages, we revert this change for following tests
 from MoinMoin.script.migration._conv160_wiki import convert_wiki
+i18n.wikiLanguages = i18n_wikiLanguages
 
 class TestWikiConversion:
     """ test the wiki markup conversion 1.5.8 -> 1.6.0 """
@@ -111,7 +115,7 @@
             # macros
             ('[[BR]]', {}, '<<BR>>'),
             ('[[FullSearch(wtf)]]', {}, '<<FullSearch(wtf)>>'),
-            (u'[[ImageLink(tst.png)]]', {}, u'[[attachment:tst.png|{{attachment:tst.png}}]]'),
+            (u'[[ImageLink(töst.png)]]', {}, u'[[attachment:töst.png|{{attachment:töst.png}}]]'),
             ('[[ImageLink(test.png,OtherPage)]]', {}, '[[OtherPage|{{attachment:test.png}}]]'),
             ('[[ImageLink(test.png,OtherPage,width=123,height=456)]]', {}, '[[OtherPage|{{attachment:test.png||width=123, height=456}}]]'),
             ('[[ImageLink(test.png,OtherPage,width=123,height=456,alt=alttext)]]', {}, '[[OtherPage|{{attachment:test.png|alttext|width=123, height=456}}]]'),
--- a/MoinMoin/script/migration/_tests/test_conv160a_wiki.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/script/migration/_tests/test_conv160a_wiki.py	Mon Aug 31 18:59:51 2009 +0200
@@ -20,7 +20,11 @@
 import py
 #py.test.skip("broken")
 
+from MoinMoin import i18n
+i18n_wikiLanguages = i18n.wikiLanguages
+# convert_wiki overwrites i18n.wikiLanguages, we revert this change for following tests
 from MoinMoin.script.migration._conv160a_wiki import convert_wiki
+i18n.wikiLanguages = i18n_wikiLanguages
 
 class TestWikiConversion:
     """ test the wiki markup conversion 1.6.0a -> 1.6.0 """
--- a/MoinMoin/script/server/standalone.py	Tue Aug 18 18:03:36 2009 +0200
+++ b/MoinMoin/script/server/standalone.py	Mon Aug 31 18:59:51 2009 +0200
@@ -34,8 +34,8 @@
             help="Set the port to listen on. Default: 8080"
         )
         self.parser.add_option(
-            "--interface", dest="interface",
-            help="Set the ip to listen on. Use \"\" for all interfaces. Default: localhost"
+            "--hostname", "--interface", dest="hostname",
+            help="Set the ip/hostname to listen on. Use \"\" for all interfaces. Default: localhost"
         )
         self.parser.add_option(
             "--start", dest="start", action="store_true",
@@ -88,33 +88,43 @@
                     raise
 
             # intialize some defaults if missing
-            for option in ('docs', 'user', 'group', 'port', 'interface', 'debug'):
-                if not hasattr(Config, option):
-                    value = getattr(DefaultConfig, option)
-                    setattr(Config, option, value)
+            kwargs = {}
+            for option in ('user', 'group',
+                           'hostname', 'port',
+                           'threaded', 'processes',
+                           'debug', 'use_evalex',
+                           'use_reloader', 'extra_files', 'reloader_interval',
+                           'docs', 'static_files', ):
+                if hasattr(Config, option):
+                    kwargs[option] = getattr(Config, option)
+                else:
+                    # usually inheriting from DefaultConfig should make this superfluous,
+                    # but who knows what users write into their config...
+                    kwargs[option] = getattr(DefaultConfig, option)
 
-            # override with cmdline options
+            # override config settings with cmdline options:
             if self.options.docs:
-                Config.docs = self.options.docs
+                kwargs['docs'] = self.options.docs
             if self.options.user:
-                Config.user = self.options.user
+                kwargs['user'] = self.options.user
             if self.options.group:
-                Config.group = self.options.group
+                kwargs['group'] = self.options.group
+            if self.options.debug:
+                kwargs['debug'] = self.options.debug
+
+            if self.options.hostname is not None: # needs to work for "" value also
+                kwargs['hostname'] = self.options.hostname
             if self.options.port:
-                Config.port = self.options.port
-            if self.options.interface is not None: # needs to work for "" value also
-                Config.interface = self.options.interface
-            if self.options.debug:
-                Config.debug = self.options.debug
+                kwargs['port'] = self.options.port
 
             if self.options.start:
-                daemon = Daemon('moin', pidfile, run_server, Config)
+                daemon = Daemon('moin', pidfile, run_server, **kwargs)
                 daemon.do_start()
             else:
-                run_server(Config.interface, Config.port, Config.docs,
-                           debug=Config.debug, user=Config.user, group=Config.group)
+                run_server(**kwargs)
 
-class DefaultConfig:
+
+class DefaultConfig(object):
     # where the static data is served from - you can either use:
     # docs = True  # serve the builtin static data from MoinMoin/web/static/htdocs/
     # docs = '/where/ever/you/like/to/keep/htdocs'  # serve it from the given path
@@ -122,9 +132,38 @@
     #               # you serve them in some other working way)
     docs = True
 
+    # user and group to run moin as:
     user = None
     group = None
-    port = 8080
-    interface = 'localhost'
+
+    # debugging options: 'off', 'web', 'external'
     debug = 'off'
 
+    # should the exception evaluation feature be enabled?
+    use_evalex = True
+
+    # Werkzeug run_simple arguments below here:
+
+    # hostname/ip and port the server listens on:
+    hostname = 'localhost'
+    port = 8080
+
+    # either multi-thread or multi-process (not both):
+    # threaded = True, processes = 1 is usually what you want
+    # threaded = False, processes = 10 (for example) can be rather slow
+    # thus, if you need a forking server, maybe rather use apache/mod-wsgi!
+    threaded = True
+    processes = 1
+
+    # automatic code reloader - needs testing!
+    use_reloader = False
+    extra_files = None
+    reloader_interval = 1
+
+    # we can't use static_files to replace our own middleware setup for moin's
+    # static files, because we also need the setup with other servers (like
+    # apache), not just when using werkzeug's run_simple server.
+    # But you can use it if you need to serve other static files you just need
+    # with the standalone wikiserver.
+    static_files = None
+
--- a/MoinMoin/search/Xapian.py	Tue Aug 18 18:03:36 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,655 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - xapian search engine
-
-    @copyright: 2006-2008 MoinMoin:ThomasWaldmann,
-                2006 MoinMoin:FranzPletz
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os, re
-
-import xapian
-from xapian import Query
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.support.xapwrap import document as xapdoc
-from MoinMoin.support.xapwrap import index as xapidx
-from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
-
-from MoinMoin.Page import Page
-from MoinMoin import config, wikiutil
-from MoinMoin.search.builtin import BaseIndex
-
-
-class UnicodeQuery(Query):
-    """ Xapian query object which automatically encodes unicode strings """
-    def __init__(self, *args, **kwargs):
-        """
-        @keyword encoding: specifiy the encoding manually (default: value of config.charset)
-        """
-        self.encoding = kwargs.get('encoding', config.charset)
-
-        nargs = []
-        for term in args:
-            if isinstance(term, unicode):
-                term = term.encode(self.encoding)
-            elif isinstance(term, list) or isinstance(term, tuple):
-                term = [t.encode(self.encoding) for t in term]
-            nargs.append(term)
-
-        Query.__init__(self, *nargs, **kwargs)
-
-
-##############################################################################
-### Tokenizer
-##############################################################################
-
-def getWikiAnalyzerFactory(request=None, language='en'):
-    """ Returns a WikiAnalyzer instance
-
-    @keyword request: current request object
-    @keyword language: stemming language iso code, defaults to 'en'
-    """
-    return (lambda: WikiAnalyzer(request, language))
-
-class WikiAnalyzer:
-    """ A text analyzer for wiki syntax
-