changeset 1120:72a208bfe579

Merge with main.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Wed, 02 Aug 2006 01:17:25 +0200
parents 397b97122ad9 (current diff) d25b3f1b25f3 (diff)
children 91ffa85ac616 84f94820d612
files
diffstat 15 files changed, 392 insertions(+), 313 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/__init__.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/__init__.py	Wed Aug 02 01:17:25 2006 +0200
@@ -1,6 +1,6 @@
 # -*- coding: iso-8859-1 -*-
 """
-MoinMoin Version 1.6.0alpha 61142a50c41b+ tip
+MoinMoin Version 1.6.0alpha b27d720fbc8e tip
 
 @copyright: 2000-2006 by Jürgen Hermann <jh@web.de>
 @license: GNU GPL, see COPYING for details.
--- a/MoinMoin/action/AttachFile.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/action/AttachFile.py	Wed Aug 02 01:17:25 2006 +0200
@@ -29,7 +29,7 @@
 import os, time, zipfile
 from MoinMoin import config, user, util, wikiutil, packages
 from MoinMoin.Page import Page
-from MoinMoin.util import filesys
+from MoinMoin.util import filesys, timefuncs
 
 action_name = __name__.split('.')[-1]
 
@@ -651,18 +651,22 @@
     if not filename:
         return # error msg already sent in _access_file
 
-    mt = wikiutil.MimeType(filename=filename)
+    timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
+    if request.if_modified_since == timestamp:
+        request.emit_http_headers(["Status: 304 Not modified"])
+    else:
+        mt = wikiutil.MimeType(filename=filename)
+        request.emit_http_headers([
+            "Content-Type: %s" % mt.content_type(),
+            "Last-Modified: %s" % timestamp, # TODO maybe add a short Expires: header here?
+            "Content-Length: %d" % os.path.getsize(fpath),
+            # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
+            # There is no solution that is compatible to IE except stripping non-ascii chars
+            "Content-Disposition: attachment; filename=\"%s\"" % filename.encode(config.charset),
+        ])
 
-    request.emit_http_headers([
-        "Content-Type: %s" % mt.content_type(),
-        "Content-Length: %d" % os.path.getsize(fpath),
-        # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
-        # There is no solution that is compatible to IE except stripping non-ascii chars
-        "Content-Disposition: attachment; filename=\"%s\"" % filename.encode(config.charset),
-    ])
-
-    # send data
-    shutil.copyfileobj(open(fpath, 'rb'), request, 8192)
+        # send data
+        shutil.copyfileobj(open(fpath, 'rb'), request, 8192)
 
 def install_package(pagename, request):
     _ = request.getText
--- a/MoinMoin/action/__init__.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/action/__init__.py	Wed Aug 02 01:17:25 2006 +0200
@@ -201,15 +201,8 @@
         else:
             self.render_msg(self.make_form()) # display the form again
 
-# from wikiaction.py ---------------------------------------------------------
 
-import os, re, time
-from MoinMoin import config, util
-from MoinMoin.logfile import editlog
-
-#############################################################################
-### Misc Actions
-#############################################################################
+# Builtin Actions ------------------------------------------------------------
 
 def do_raw(pagename, request):
     """ send raw content of a page (e.g. wiki markup) """
@@ -279,160 +272,19 @@
     caching.CacheEntry(request, arena, "pagelinks", scope='item').remove()
     do_show(pagename, request)
 
-def do_revert(pagename, request):
-    """ restore another revision of a page as a new current revision """
-    from MoinMoin.PageEditor import PageEditor
-    _ = request.getText
-
-    if not request.user.may.revert(pagename):
-        return Page(request, pagename).send_page(request,
-            msg=_('You are not allowed to revert this page!'))
-
-    rev = int(request.form['rev'][0])
-    revstr = '%08d' % rev
-    oldpg = Page(request, pagename, rev=rev)
-    pg = PageEditor(request, pagename)
-
-    try:
-        savemsg = pg.saveText(oldpg.get_raw_body(), 0, extra=revstr,
-                              action="SAVE/REVERT")
-    except pg.SaveError, msg:
-        # msg contain a unicode string
-        savemsg = unicode(msg)
-    request.reset()
-    pg.send_page(request, msg=savemsg)
-    return None
-
 def do_goto(pagename, request):
     """ redirect to another page """
     target = request.form.get('target', [''])[0]
     request.http_redirect(Page(request, target).url(request))
 
-def do_quicklink(pagename, request):
-    """ Add the current wiki page to the user quicklinks 
-    
-    TODO: what if add or remove quicklink fail? display an error message?
-    """
-    _ = request.getText
-    msg = None
-
-    if not request.user.valid:
-        msg = _("You must login to add a quicklink.")
-    elif request.user.isQuickLinkedTo([pagename]):
-        if request.user.removeQuicklink(pagename):
-            msg = _('Your quicklink to this page has been removed.')
-    else:
-        if request.user.addQuicklink(pagename):
-            msg = _('A quicklink to this page has been added for you.')
-
-    Page(request, pagename).send_page(request, msg=msg)
-
-def do_subscribe(pagename, request):
-    """ Subscribe or unsubscribe the user to pagename
-    
-    TODO: what if subscribe failed? no message is displayed.
-    """
-    _ = request.getText
-    cfg = request.cfg
-    msg = None
-
-    if not request.user.may.read(pagename):
-        msg = _("You are not allowed to subscribe to a page you can't read.")
-
-    # Check if mail is enabled
-    elif not cfg.mail_enabled:
-        msg = _("This wiki is not enabled for mail processing.")
-
-    # Suggest visitors to login
-    elif not request.user.valid:
-        msg = _("You must log in to use subscribtions.")
-
-    # Suggest users without email to add their email address
-    elif not request.user.email:
-        msg = _("Add your email address in your UserPreferences to use subscriptions.")
-
-    elif request.user.isSubscribedTo([pagename]):
-        # Try to unsubscribe
-        if request.user.unsubscribe(pagename):
-            msg = _('Your subscribtion to this page has been removed.')
-        else:
-            msg = _("Can't remove regular expression subscription!") + u' ' + \
-                  _("Edit the subscription regular expressions in your "
-                    "UserPreferences.")
-
-    else:
-        # Try to subscribe
-        if request.user.subscribe(pagename):
-            msg = _('You have been subscribed to this page.')
-
-    Page(request, pagename).send_page(request, msg=msg)
-
 def do_userform(pagename, request):
     """ save data posted from UserPreferences """
     from MoinMoin import userform
     savemsg = userform.savedata(request)
     Page(request, pagename).send_page(request, msg=savemsg)
 
-def do_bookmark(pagename, request):
-    """ set bookmarks (in time) for RecentChanges or delete them """
-    timestamp = request.form.get('time', [None])[0]
-    if timestamp is not None:
-        if timestamp == 'del':
-            tm = None
-        else:
-            try:
-                tm = int(timestamp)
-            except StandardError:
-                tm = wikiutil.timestamp2version(time.time())
-    else:
-        tm = wikiutil.timestamp2version(time.time())
-
-    if tm is None:
-        request.user.delBookmark()
-    else:
-        request.user.setBookmark(tm)
-    Page(request, pagename).send_page(request)
-
-
-#############################################################################
-### Special Actions
-#############################################################################
-
-def do_chart(pagename, request):
-    """ Show page charts """
-    _ = request.getText
-    if not request.user.may.read(pagename):
-        msg = _("You are not allowed to view this page.")
-        return request.page.send_page(request, msg=msg)
-
-    if not request.cfg.chart_options:
-        msg = _("Charts are not available!")
-        return request.page.send_page(request, msg=msg)
-
-    chart_type = request.form.get('type', [''])[0].strip()
-    if not chart_type:
-        msg = _('You need to provide a chart type!')
-        return request.page.send_page(request, msg=msg)
-
-    try:
-        func = pysupport.importName("MoinMoin.stats." + chart_type, 'draw')
-    except (ImportError, AttributeError):
-        msg = _('Bad chart type "%s"!') % chart_type
-        return request.page.send_page(request, msg=msg)
-
-    func(pagename, request)
-
-def do_dumpform(pagename, request):
-    """ dump the form data we received in this request for debugging """
-    data = util.dumpFormData(request.form)
-
-    request.emit_http_headers()
-    request.write("<html><body>%s</body></html>" % data)
-
-
-#############################################################################
-### Dispatching
-#############################################################################
+# Dispatching ----------------------------------------------------------------
+import os
 
 def getPlugins(request):
     """ return the path to the action plugin directory and a list of plugins there """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/bookmark.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,33 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - set or delete bookmarks (in time) for RecentChanges
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import time
+
+from MoinMoin import wikiutil
+from MoinMoin.Page import Page
+
+def execute(pagename, request):
+    """ set bookmarks (in time) for RecentChanges or delete them """
+    timestamp = request.form.get('time', [None])[0]
+    if timestamp is not None:
+        if timestamp == 'del':
+            tm = None
+        else:
+            try:
+                tm = int(timestamp)
+            except StandardError:
+                tm = wikiutil.timestamp2version(time.time())
+    else:
+        tm = wikiutil.timestamp2version(time.time())
+
+    if tm is None:
+        request.user.delBookmark()
+    else:
+        request.user.setBookmark(tm)
+    request.page.send_page(request)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/chart.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,34 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - show some statistics chart
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin.util import pysupport
+
+def execute(pagename, request):
+    """ Show page charts """
+    _ = request.getText
+    if not request.user.may.read(pagename):
+        msg = _("You are not allowed to view this page.")
+        return request.page.send_page(request, msg=msg)
+
+    if not request.cfg.chart_options:
+        msg = _("Charts are not available!")
+        return request.page.send_page(request, msg=msg)
+
+    chart_type = request.form.get('type', [''])[0].strip()
+    if not chart_type:
+        msg = _('You need to provide a chart type!')
+        return request.page.send_page(request, msg=msg)
+
+    try:
+        func = pysupport.importName("MoinMoin.stats.%s" % chart_type, 'draw')
+    except (ImportError, AttributeError), err:
+        msg = _('Bad chart type "%s"!') % chart_type
+        return request.page.send_page(request, msg=msg)
+
+    func(pagename, request)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/dumpform.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,17 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - dump form data we received (debugging)
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin import util
+
+def execute(pagename, request):
+    """ dump the form data we received in this request for debugging """
+    data = util.dumpFormData(request.form)
+
+    request.emit_http_headers()
+    request.write("<html><body>%s</body></html>" % data)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/quicklink.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,29 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - add a quicklink to the user's quicklinks
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin.Page import Page
+
+def execute(pagename, request):
+    """ Add the current wiki page to the user quicklinks 
+    
+    TODO: what if add or remove quicklink fail? display an error message?
+    """
+    _ = request.getText
+    msg = None
+
+    if not request.user.valid:
+        msg = _("You must login to add a quicklink.")
+    elif request.user.isQuickLinkedTo([pagename]):
+        if request.user.removeQuicklink(pagename):
+            msg = _('Your quicklink to this page has been removed.')
+    else:
+        if request.user.addQuicklink(pagename):
+            msg = _('A quicklink to this page has been added for you.')
+
+    Page(request, pagename).send_page(request, msg=msg)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/revert.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,33 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - revert a page to a previous revision
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin.Page import Page
+
+def execute(pagename, request):
+    """ restore another revision of a page as a new current revision """
+    from MoinMoin.PageEditor import PageEditor
+    _ = request.getText
+
+    if not request.user.may.revert(pagename):
+        return Page(request, pagename).send_page(request,
+            msg=_('You are not allowed to revert this page!'))
+
+    rev = int(request.form['rev'][0])
+    revstr = '%08d' % rev
+    oldpg = Page(request, pagename, rev=rev)
+    pg = PageEditor(request, pagename)
+
+    try:
+        savemsg = pg.saveText(oldpg.get_raw_body(), 0, extra=revstr,
+                              action="SAVE/REVERT")
+    except pg.SaveError, msg:
+        # msg contain a unicode string
+        savemsg = unicode(msg)
+    request.reset()
+    pg.send_page(request, msg=savemsg)
+
--- a/MoinMoin/action/rss_rc.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/action/rss_rc.py	Wed Aug 02 01:17:25 2006 +0200
@@ -45,18 +45,7 @@
     except ValueError:
         ddiffs = 0
 
-    # prepare output
-    out = StringIO.StringIO()
-    handler = RssGenerator(out)
-
     # get data
-    interwiki = request.getBaseURL()
-    if interwiki[-1] != "/": interwiki = interwiki + "/"
-
-    logo = re.search(r'src="([^"]*)"', cfg.logo_string)
-    if logo:
-        logo = request.getQualifiedURL(logo.group(1))
-
     log = editlog.EditLog(request)
     logdata = []
     counter = 0
@@ -81,145 +70,173 @@
             break
     del log
 
-    # start SAX stream
-    handler.startDocument()
-    handler._out.write(
-        '<!--\n'
-        '    Add an "items=nnn" URL parameter to get more than the default 15 items.\n'
-        '    You cannot get more than %d items though.\n'
-        '    \n'
-        '    Add "unique=1" to get a list of changes where page names are unique,\n'
-        '    i.e. where only the latest change of each page is reflected.\n'
-        '    \n'
-        '    Add "diffs=1" to add change diffs to the description of each items.\n'
-        '    \n'
-        '    Add "ddiffs=1" to link directly to the diff (good for FeedReader).\n'
-        '    Current settings: items=%i, unique=%i, diffs=%i, ddiffs=%i'
-        '-->\n' % (items_limit, max_items, unique, diffs, ddiffs)
-        )
+    timestamp = timefuncs.formathttpdate(lastmod)
+    etag = "%d-%d-%d-%d-%d" % (lastmod, max_items, diffs, ddiffs, unique)
 
-    # emit channel description
-    handler.startNode('channel', {
-        (handler.xmlns['rdf'], 'about'): request.getBaseURL(),
-        })
-    handler.simpleNode('title', cfg.sitename)
-    handler.simpleNode('link', interwiki + wikiutil.quoteWikinameURL(pagename))
-    handler.simpleNode('description', 'RecentChanges at %s' % cfg.sitename)
-    if logo:
-        handler.simpleNode('image', None, {
-            (handler.xmlns['rdf'], 'resource'): logo,
-            })
-    if cfg.interwikiname:
-        handler.simpleNode(('wiki', 'interwiki'), cfg.interwikiname)
+    # for 304, we look at if-modified-since and if-none-match headers,
+    # one of them must match and the other is either not there or must match.
+    if request.if_modified_since == timestamp:
+        if request.if_none_match:
+            if request.if_none_match == etag:
+                request.emit_http_headers(["Status: 304 Not modified"])
+        else:
+            request.emit_http_headers(["Status: 304 Not modified"])
+    elif request.if_none_match == etag:
+        if request.if_modified_since:
+            if request.if_modified_since == timestamp:
+                request.emit_http_headers(["Status: 304 Not modified"])
+        else:
+            request.emit_http_headers(["Status: 304 Not modified"])
+    else:
+        # generate an Expires header, using whatever setting the admin
+        # defined for suggested cache lifetime of the RecentChanges RSS doc
+        expires = timefuncs.formathttpdate(time.time() + cfg.rss_cache)
 
-    handler.startNode('items')
-    handler.startNode(('rdf', 'Seq'))
-    for item in logdata:
-        link = "%s%s#%04d%02d%02d%02d%02d%02d" % ((interwiki,
-                wikiutil.quoteWikinameURL(item.pagename),) + item.time[:6])
-        handler.simpleNode(('rdf', 'li'), None, attr={
-            (handler.xmlns['rdf'], 'resource'): link,
-        })
-    handler.endNode(('rdf', 'Seq'))
-    handler.endNode('items')
-    handler.endNode('channel')
+        httpheaders = ["Content-Type: text/xml; charset=%s" % config.charset,
+                       "Expires: %s" % expires,
+                       "Last-Modified: %s" % timestamp,
+                       "Etag: %s" % etag, ]
 
-    # emit logo data
-    if logo:
-        handler.startNode('image', attr={
-            (handler.xmlns['rdf'], 'about'): logo,
+        # send the generated XML document
+        request.emit_http_headers(httpheaders)
+
+        interwiki = request.getBaseURL()
+        if interwiki[-1] != "/":
+            interwiki = interwiki + "/"
+
+        logo = re.search(r'src="([^"]*)"', cfg.logo_string)
+        if logo:
+            logo = request.getQualifiedURL(logo.group(1))
+
+        # prepare output
+        out = StringIO.StringIO()
+        handler = RssGenerator(out)
+
+        # start SAX stream
+        handler.startDocument()
+        handler._out.write(
+            '<!--\n'
+            '    Add an "items=nnn" URL parameter to get more than the default 15 items.\n'
+            '    You cannot get more than %d items though.\n'
+            '    \n'
+            '    Add "unique=1" to get a list of changes where page names are unique,\n'
+            '    i.e. where only the latest change of each page is reflected.\n'
+            '    \n'
+            '    Add "diffs=1" to add change diffs to the description of each items.\n'
+            '    \n'
+            '    Add "ddiffs=1" to link directly to the diff (good for FeedReader).\n'
+            '    Current settings: items=%i, unique=%i, diffs=%i, ddiffs=%i'
+            '-->\n' % (items_limit, max_items, unique, diffs, ddiffs)
+            )
+
+        # emit channel description
+        handler.startNode('channel', {
+            (handler.xmlns['rdf'], 'about'): request.getBaseURL(),
             })
         handler.simpleNode('title', cfg.sitename)
-        handler.simpleNode('link', interwiki)
-        handler.simpleNode('url', logo)
-        handler.endNode('image')
-
-    # emit items
-    for item in logdata:
-        page = Page(request, item.pagename)
-        link = interwiki + wikiutil.quoteWikinameURL(item.pagename)
-        rdflink = "%s#%04d%02d%02d%02d%02d%02d" % ((link,) + item.time[:6])
-        handler.startNode('item', attr={
-            (handler.xmlns['rdf'], 'about'): rdflink,
-        })
-
-        # general attributes
-        handler.simpleNode('title', item.pagename)
-        if ddiffs:
-            handler.simpleNode('link', link+"?action=diff")
-        else:
-            handler.simpleNode('link', link)
-
-        handler.simpleNode(('dc', 'date'), timefuncs.W3CDate(item.time))
-
-        # description
-        desc_text = item.comment
-        if diffs:
-            # TODO: rewrite / extend wikiutil.pagediff
-            # searching for the matching pages doesn't really belong here
-            revisions = page.getRevList()
-
-            rl = len(revisions)
-            for idx in range(rl):
-                rev = revisions[idx]
-                if rev <= item.rev:
-                    if idx+1 < rl:
-                        lines = wikiutil.pagediff(request, item.pagename, revisions[idx+1], item.pagename, 0, ignorews=1)
-                        if len(lines) > 20:
-                            lines = lines[:20] + ['...\n']
-                        lines = '\n'.join(lines)
-                        lines = wikiutil.escape(lines)
-                        desc_text = '%s\n<pre>\n%s\n</pre>\n' % (desc_text, lines)
-                    break
-        if desc_text:
-            handler.simpleNode('description', desc_text)
+        handler.simpleNode('link', interwiki + wikiutil.quoteWikinameURL(pagename))
+        handler.simpleNode('description', 'RecentChanges at %s' % cfg.sitename)
+        if logo:
+            handler.simpleNode('image', None, {
+                (handler.xmlns['rdf'], 'resource'): logo,
+                })
+        if cfg.interwikiname:
+            handler.simpleNode(('wiki', 'interwiki'), cfg.interwikiname)
 
-        # contributor
-        edattr = {}
-        if cfg.show_hosts:
-            edattr[(handler.xmlns['wiki'], 'host')] = item.hostname
-        if item.editor[0] == 'interwiki':
-            edname = "%s:%s" % item.editor[1]
-            ##edattr[(None, 'link')] = interwiki + wikiutil.quoteWikiname(edname)
-        else: # 'ip'
-            edname = item.editor[1]
-            ##edattr[(None, 'link')] = link + "?action=info"
-
-        # this edattr stuff, esp. None as first tuple element breaks things (tracebacks)
-        # if you know how to do this right, please send us a patch
-
-        handler.startNode(('dc', 'contributor'))
-        handler.startNode(('rdf', 'Description'), attr=edattr)
-        handler.simpleNode(('rdf', 'value'), edname)
-        handler.endNode(('rdf', 'Description'))
-        handler.endNode(('dc', 'contributor'))
+        handler.startNode('items')
+        handler.startNode(('rdf', 'Seq'))
+        for item in logdata:
+            link = "%s%s#%04d%02d%02d%02d%02d%02d" % ((interwiki,
+                    wikiutil.quoteWikinameURL(item.pagename),) + item.time[:6])
+            handler.simpleNode(('rdf', 'li'), None, attr={
+                (handler.xmlns['rdf'], 'resource'): link,
+            })
+        handler.endNode(('rdf', 'Seq'))
+        handler.endNode('items')
+        handler.endNode('channel')
 
-        # wiki extensions
-        handler.simpleNode(('wiki', 'version'), "%i" % (item.ed_time_usecs))
-        handler.simpleNode(('wiki', 'status'), ('deleted', 'updated')[page.exists()])
-        handler.simpleNode(('wiki', 'diff'), link + "?action=diff")
-        handler.simpleNode(('wiki', 'history'), link + "?action=info")
-        # handler.simpleNode(('wiki', 'importance'), ) # ( major | minor ) 
-        # handler.simpleNode(('wiki', 'version'), ) # ( #PCDATA ) 
-
-        handler.endNode('item')
-
-    # end SAX stream
-    handler.endDocument()
+        # emit logo data
+        if logo:
+            handler.startNode('image', attr={
+                (handler.xmlns['rdf'], 'about'): logo,
+                })
+            handler.simpleNode('title', cfg.sitename)
+            handler.simpleNode('link', interwiki)
+            handler.simpleNode('url', logo)
+            handler.endNode('image')
 
-    # generate an Expires header, using whatever setting the admin
-    # defined for suggested cache lifetime of the RecentChanges RSS doc
-    expires = timefuncs.formathttpdate(time.time() + cfg.rss_cache)
-
-    httpheaders = ["Content-Type: text/xml; charset=%s" % config.charset,
-                   "Expires: %s" % expires]
+        # emit items
+        for item in logdata:
+            page = Page(request, item.pagename)
+            link = interwiki + wikiutil.quoteWikinameURL(item.pagename)
+            rdflink = "%s#%04d%02d%02d%02d%02d%02d" % ((link,) + item.time[:6])
+            handler.startNode('item', attr={
+                (handler.xmlns['rdf'], 'about'): rdflink,
+            })
 
-    # use a correct Last-Modified header, set to whatever the mod date
-    # on the most recent page was; if there were no mods, don't send one
-    if lastmod:
-        httpheaders.append("Last-Modified: %s" % timefuncs.formathttpdate(lastmod))
+            # general attributes
+            handler.simpleNode('title', item.pagename)
+            if ddiffs:
+                handler.simpleNode('link', link+"?action=diff")
+            else:
+                handler.simpleNode('link', link)
 
-    # send the generated XML document
-    request.emit_http_headers(httpheaders)
-    request.write(out.getvalue())
+            handler.simpleNode(('dc', 'date'), timefuncs.W3CDate(item.time))
 
+            # description
+            desc_text = item.comment
+            if diffs:
+                # TODO: rewrite / extend wikiutil.pagediff
+                # searching for the matching pages doesn't really belong here
+                revisions = page.getRevList()
+
+                rl = len(revisions)
+                for idx in range(rl):
+                    rev = revisions[idx]
+                    if rev <= item.rev:
+                        if idx+1 < rl:
+                            lines = wikiutil.pagediff(request, item.pagename, revisions[idx+1], item.pagename, 0, ignorews=1)
+                            if len(lines) > 20:
+                                lines = lines[:20] + ['...\n']
+                            lines = '\n'.join(lines)
+                            lines = wikiutil.escape(lines)
+                            desc_text = '%s\n<pre>\n%s\n</pre>\n' % (desc_text, lines)
+                        break
+            if desc_text:
+                handler.simpleNode('description', desc_text)
+
+            # contributor
+            edattr = {}
+            if cfg.show_hosts:
+                edattr[(handler.xmlns['wiki'], 'host')] = item.hostname
+            if item.editor[0] == 'interwiki':
+                edname = "%s:%s" % item.editor[1]
+                ##edattr[(None, 'link')] = interwiki + wikiutil.quoteWikiname(edname)
+            else: # 'ip'
+                edname = item.editor[1]
+                ##edattr[(None, 'link')] = link + "?action=info"
+
+            # this edattr stuff, esp. None as first tuple element breaks things (tracebacks)
+            # if you know how to do this right, please send us a patch
+
+            handler.startNode(('dc', 'contributor'))
+            handler.startNode(('rdf', 'Description'), attr=edattr)
+            handler.simpleNode(('rdf', 'value'), edname)
+            handler.endNode(('rdf', 'Description'))
+            handler.endNode(('dc', 'contributor'))
+
+            # wiki extensions
+            handler.simpleNode(('wiki', 'version'), "%i" % (item.ed_time_usecs))
+            handler.simpleNode(('wiki', 'status'), ('deleted', 'updated')[page.exists()])
+            handler.simpleNode(('wiki', 'diff'), link + "?action=diff")
+            handler.simpleNode(('wiki', 'history'), link + "?action=info")
+            # handler.simpleNode(('wiki', 'importance'), ) # ( major | minor ) 
+            # handler.simpleNode(('wiki', 'version'), ) # ( #PCDATA ) 
+
+            handler.endNode('item')
+
+        # end SAX stream
+        handler.endDocument()
+
+        request.write(out.getvalue())
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/subscribe.py	Wed Aug 02 01:17:25 2006 +0200
@@ -0,0 +1,50 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - subscribe to a page to get notified when it changes
+
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin.Page import Page
+
+def execute(pagename, request):
+    """ Subscribe or unsubscribe the user to pagename
+    
+    TODO: what if subscribe failed? no message is displayed.
+    """
+    _ = request.getText
+    cfg = request.cfg
+    msg = None
+
+    if not request.user.may.read(pagename):
+        msg = _("You are not allowed to subscribe to a page you can't read.")
+
+    # Check if mail is enabled
+    elif not cfg.mail_enabled:
+        msg = _("This wiki is not enabled for mail processing.")
+
+    # Suggest visitors to login
+    elif not request.user.valid:
+        msg = _("You must log in to use subscribtions.")
+
+    # Suggest users without email to add their email address
+    elif not request.user.email:
+        msg = _("Add your email address in your UserPreferences to use subscriptions.")
+
+    elif request.user.isSubscribedTo([pagename]):
+        # Try to unsubscribe
+        if request.user.unsubscribe(pagename):
+            msg = _('Your subscribtion to this page has been removed.')
+        else:
+            msg = _("Can't remove regular expression subscription!") + u' ' + \
+                  _("Edit the subscription regular expressions in your "
+                    "UserPreferences.")
+
+    else:
+        # Try to subscribe
+        if request.user.subscribe(pagename):
+            msg = _('You have been subscribed to this page.')
+
+    Page(request, pagename).send_page(request, msg=msg)
+
--- a/MoinMoin/request/CLI.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/request/CLI.py	Wed Aug 02 01:17:25 2006 +0200
@@ -26,6 +26,8 @@
         self.http_host = 'localhost'
         self.http_referer = ''
         self.script_name = '.'
+        self.if_modified_since = None
+        self.if_none_match = None
         RequestBase.__init__(self, properties)
         self.cfg.caching_formats = [] # don't spoil the cache
         self.initTheme() # usually request.run() does this, but we don't use it
--- a/MoinMoin/request/STANDALONE.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/request/STANDALONE.py	Wed Aug 02 01:17:25 2006 +0200
@@ -33,6 +33,10 @@
             self.http_user_agent = sa.headers.getheader('user-agent', '')
             co = filter(None, sa.headers.getheaders('cookie'))
             self.saved_cookie = ', '.join(co) or ''
+            self.if_modified_since = (sa.headers.getheader('if-modified-since')
+                                      or self.if_modified_since)
+            self.if_none_match = (sa.headers.getheader('if-none-match')
+                                  or self.if_none_match)
 
             # Copy rest from standalone request   
             self.server_name = sa.server.server_name
--- a/MoinMoin/request/TWISTED.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/request/TWISTED.py	Wed Aug 02 01:17:25 2006 +0200
@@ -23,6 +23,8 @@
             self.http_accept_language = self.twistd.getHeader('Accept-Language')
             self.saved_cookie = self.twistd.getHeader('Cookie')
             self.http_user_agent = self.twistd.getHeader('User-Agent')
+            self.if_modified_since = self.twistd.getHeader('If-Modified-Since')
+            self.if_none_match = self.twistd.getHeader('If-None-Match')
 
             # Copy values from twisted request
             self.server_protocol = self.twistd.clientproto
--- a/MoinMoin/request/__init__.py	Wed Aug 02 01:12:42 2006 +0200
+++ b/MoinMoin/request/__init__.py	Wed Aug 02 01:17:25 2006 +0200
@@ -315,8 +315,7 @@
         """
         # Values we can just copy
         self.env = env
-        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE',
-                                            self.http_accept_language)
+        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE', self.http_accept_language)
         self.server_name = env.get('SERVER_NAME', self.server_name)
         self.server_port = env.get('SERVER_PORT', self.server_port)
         self.saved_cookie = env.get('HTTP_COOKIE', '')
@@ -326,6 +325,8 @@
         self.request_method = env.get('REQUEST_METHOD', None)
         self.remote_addr = env.get('REMOTE_ADDR', '')
         self.http_user_agent = env.get('HTTP_USER_AGENT', '')
+        self.if_modified_since = env.get('If-modified-since') or env.get(cgiMetaVariable('If-modified-since'))
+        self.if_none_match = env.get('If-none-match') or env.get(cgiMetaVariable('If-none-match'))
 
         # REQUEST_URI is not part of CGI spec, but an addition of Apache.
         self.request_uri = env.get('REQUEST_URI', '')
@@ -336,8 +337,7 @@
         self.setHost(env.get('HTTP_HOST'))
         self.fixURI(env)
         self.setURL(env)
-
-        ##self.debugEnvironment(env)
+        #self.debugEnvironment(env)
 
     def setHttpReferer(self, referer):
         """ Set http_referer, making sure its ascii
@@ -1221,11 +1221,10 @@
 
         @param err: Exception instance or subclass.
         """
-        self.failed = 1 # save state for self.run()
+        self.failed = 1 # save state for self.run()            
         # we should not generate the headers two times
         if not getattr(self, 'sent_headers', 0):
             self.emit_http_headers(['Status: 500 MoinMoin Internal Error'])
-        #self.setResponseCode(500)
         self.log('%s: %s' % (err.__class__.__name__, str(err)))
         from MoinMoin import failure
         failure.handle(self)
@@ -1362,7 +1361,7 @@
             environment.append('  %s = %r\n' % (key, env[key]))
         environment = ''.join(environment)
 
-        data = '\nRequest Attributes\n%s\nEnviroment\n%s' % (attributes, environment)
+        data = '\nRequest Attributes\n%s\nEnvironment\n%s' % (attributes, environment)
         f = open('/tmp/env.log', 'a')
         try:
             f.write(data)
--- a/docs/CHANGES	Wed Aug 02 01:12:42 2006 +0200
+++ b/docs/CHANGES	Wed Aug 02 01:17:25 2006 +0200
@@ -184,6 +184,8 @@
       and caches the results (farm wide cache/i18n/*).
     * added the diff parser from ParserMarket, thanks to Emilio Lopes, Fabien
       Ninoles and Jrgen Hermann.
+    * Added support for "304 not modified" response header for AttachFile get
+      and rss_rc actions - faster, less traffic, less load.
 
   Bugfixes:
     * on action "info" page, "revert" link will not be displayed for empty page
@@ -205,6 +207,7 @@
     * BadContent and LocalBadContent now get noindex,nofollow robots header,
       same as POSTs.
     * Fixed handling of anchors in wiki links for the Restructured text parser.
+    * Fixed http header output.
 
   Other changes:
     * we use (again) the same browser compatibility check as FCKeditor uses