------------------------------------------------------------
revno: 6634
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: 3.0
timestamp: Thu 2008-07-03 00:59:12 -0400
message:
Merge the archiver plugin branch into 3.0 mainline.
added:
mailman/docs/archivers.txt
modified:
mailman/app/archiving.py
mailman/interfaces/archiver.py
mailman/pipeline/cook_headers.py
mailman/pipeline/docs/archives.txt
mailman/pipeline/docs/cook-headers.txt
mailman/pipeline/scrubber.py
mailman/queue/archive.py
setup.py
------------------------------------------------------------
revno: 6633.2.2
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: mhonarc
timestamp: Wed 2008-07-02 22:29:20 -0400
message:
Implement a prototypical archiver that supports Archived-At permalink
header,
using the current concept of the hash. This can change, but at least now
I
have the interfaces and infrastructure to support this header. Of course,
Pipermail doesn't support a permalink, so that archiver no-ops.
Add an adapter to provide the interface that Pipermail requires over and
above
the IMailingList interface. Add an is_enabled flag to IArchiver.
added:
mailman/docs/archivers.txt
modified:
mailman/app/archiving.py
mailman/interfaces/archiver.py
mailman/pipeline/cook_headers.py
mailman/pipeline/docs/cook-headers.txt
setup.py
------------------------------------------------------------
revno: 6633.2.1
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: mhonarc
timestamp: Sun 2008-06-15 00:01:55 -0400
message:
Rework the archiver interface. Remove get_primary_archiver() since now
there
can be any number of them.
get_list_url() -> list_url()
get_message_url() -> permalink()
Only add Archived-At header if mlist.archive is set.
modified:
mailman/app/archiving.py
mailman/interfaces/archiver.py
mailman/pipeline/cook_headers.py
mailman/pipeline/docs/archives.txt
mailman/pipeline/scrubber.py
mailman/queue/archive.py
setup.py
------------------------------------------------------------
revno: 6633.1.1
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: archiving
timestamp: Sat 2008-06-14 16:02:01 -0400
message:
start of archiving work
modified:
mailman/app/archiving.py
mailman/interfaces/archiver.py
mailman/pipeline/cook_headers.py
mailman/pipeline/docs/archives.txt
mailman/pipeline/scrubber.py
setup.py
=== modified file 'mailman/app/archiving.py'
--- a/mailman/app/archiving.py 2008-03-30 04:06:07 +0000
+++ b/mailman/app/archiving.py 2008-07-03 02:29:20 +0000
@@ -20,29 +20,34 @@
__metaclass__ = type
__all__ = [
'Pipermail',
- 'get_primary_archiver',
+ 'Prototype',
]
import os
-import pkg_resources
+import hashlib
+from base64 import b32encode
+from cStringIO import StringIO
+from email.utils import make_msgid
from string import Template
+from urlparse import urljoin
from zope.interface import implements
-from zope.interface.verify import verifyObject
+from zope.interface.interface import adapter_hooks
-from mailman.app.plugins import get_plugins
from mailman.configuration import config
-from mailman.interfaces import IArchiver
+from mailman.interfaces.archiver import IArchiver, IPipermailMailingList
+from mailman.interfaces.mailinglist import IMailingList
from mailman.Archiver.HyperArch import HyperArchive
-from cStringIO import StringIO
class PipermailMailingListAdapter:
"""An adapter for MailingList objects to work with Pipermail."""
+ implements(IPipermailMailingList)
+
def __init__(self, mlist):
self._mlist = mlist
@@ -50,7 +55,7 @@
return getattr(self._mlist, name)
def archive_dir(self):
- """The directory for storing Pipermail artifacts."""
+ """See `IPipermailMailingList`."""
if self._mlist.archive_private:
basedir = config.PRIVATE_ARCHIVE_FILE_DIR
else:
@@ -58,39 +63,50 @@
return os.path.join(basedir, self._mlist.fqdn_listname)
+def adapt_mailing_list_for_pipermail(iface, obj):
+ """Adapt IMailingLists to IPipermailMailingList."""
+ if IMailingList.providedBy(obj) and iface is IPipermailMailingList:
+ return PipermailMailingListAdapter(obj)
+ return None
+
+adapter_hooks.append(adapt_mailing_list_for_pipermail)
+
+
class Pipermail:
"""The stock Pipermail archiver."""
implements(IArchiver)
- def __init__(self, mlist):
- self._mlist = mlist
+ name = 'pipermail'
+ is_enabled = True
- def get_list_url(self):
+ @staticmethod
+ def list_url(mlist):
"""See `IArchiver`."""
- if self._mlist.archive_private:
- url = self._mlist.script_url('private') + '/index.html'
+ if mlist.archive_private:
+ url = mlist.script_url('private') + '/index.html'
else:
- web_host = config.domains.get(
- self._mlist.host_name, self._mlist.host_name)
+ web_host = config.domains.get(mlist.host_name, mlist.host_name)
url = Template(config.PUBLIC_ARCHIVE_URL).safe_substitute(
- listname=self._mlist.fqdn_listname,
+ listname=mlist.fqdn_listname,
hostname=web_host,
- fqdn_listname=self._mlist.fqdn_listname,
+ fqdn_listname=mlist.fqdn_listname,
)
return url
- def get_message_url(self, message):
+ @staticmethod
+ def permalink(mlist, message):
"""See `IArchiver`."""
# Not currently implemented.
return None
- def archive_message(self, message):
+ @staticmethod
+ def archive_message(mlist, message):
"""See `IArchiver`."""
text = str(message)
fileobj = StringIO(text)
- h = HyperArchive(PipermailMailingListAdapter(self._mlist))
+ h = HyperArchive(IPipermailMailingList(mlist))
h.processUnixMailbox(fileobj)
h.close()
fileobj.close()
@@ -99,12 +115,40 @@
-def get_primary_archiver(mlist):
- """Return the primary archiver."""
- entry_points = list(pkg_resources.iter_entry_points('mailman.archiver'))
- if len(entry_points) == 0:
- return None
- for ep in entry_points:
- if ep.name == 'default':
- return ep.load()(mlist)
- return None
+class Prototype:
+ """A prototype of a third party archiver.
+
+ Mailman proposes a draft specification for interoperability between list
+ servers and archivers: <http://wiki.list.org/display/DEV/Stable+URLs>.
+ """
+
+ implements(IArchiver)
+
+ name = 'prototype'
+ is_enabled = False
+
+ @staticmethod
+ def list_url(mlist):
+ """See `IArchiver`."""
+ web_host = config.domains.get(mlist.host_name, mlist.host_name)
+ return 'http://' + web_host
+
+ @staticmethod
+ def permalink(mlist, msg):
+ """See `IArchiver`."""
+ message_id = msg.get('message-id')
+ # It is not the archiver's job to ensure the message has a Message-ID.
+ assert message_id is not None, 'No Message-ID found'
+ # The angle brackets are not part of the Message-ID. See RFC 2822.
+ if message_id.startswith('<') and message_id.endswith('>'):
+ message_id = message_id[1:-1]
+ digest = hashlib.sha1(message_id).digest()
+ message_id_hash = b32encode(digest)
+ del msg['x-message-id-hash']
+ msg['X-Message-ID-Hash'] = message_id_hash
+ return urljoin(Prototype.list_url(mlist), message_id_hash)
+
+ @staticmethod
+ def archive_message(mlist, message):
+ """See `IArchiver`."""
+ raise NotImplementedError
=== added file 'mailman/docs/archivers.txt'
--- a/mailman/docs/archivers.txt 1970-01-01 00:00:00 +0000
+++ b/mailman/docs/archivers.txt 2008-07-03 02:29:20 +0000
@@ -0,0 +1,63 @@
+= Archivers =
+
+Mailman supports pluggable archivers, and it comes with several default
+archivers.
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist = create_list(u'[EMAIL PROTECTED]')
+ >>> msg = message_from_string("""\
+ ... From: [EMAIL PROTECTED]
+ ... To: [EMAIL PROTECTED]
+ ... Subject: An archived message
+ ... Message-ID: <12345>
+ ...
+ ... Here is an archived message.
+ ... """)
+
+Archivers support an interface which provides the RFC 2369 List-Archive
+header, and one that provides a 'permalink' to the specific message object in
+the archive. This latter is appropriate for the message footer or for the RFC
+5064 Archived-At header.
+
+Pipermail does not support a permalink, so that interface returns None.
+Mailman defines a draft spec for how list servers and archivers can
+interoperate.
+
+ >>> from operator import attrgetter
+ >>> name = attrgetter('name')
+ >>> from mailman.app.plugins import get_plugins
+ >>> archivers = {}
+ >>> for archiver in sorted(get_plugins('mailman.archiver'), key=name):
+ ... print archiver.name
+ ... print ' ', archiver.list_url(mlist)
+ ... print ' ', archiver.permalink(mlist, msg)
+ ... archivers[archiver.name] = archiver
+ pipermail
+ http://www.example.com/pipermail/[EMAIL PROTECTED]
+ None
+ prototype
+ http://www.example.com
+ http://www.example.com/RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE
+
+
+== Sending the message to the archiver ==
+
+The archiver is also able to archive the message.
+
+ >>> mlist.web_page_url = u'http://lists.example.com/'
+ >>> archivers['pipermail'].archive_message(mlist, msg)
+
+ >>> import os
+ >>> from mailman.interfaces.archiver import IPipermailMailingList
+ >>> pckpath = os.path.join(
+ ... IPipermailMailingList(mlist).archive_dir(),
+ ... 'pipermail.pck')
+ >>> os.path.exists(pckpath)
+ True
+
+Note however that the prototype archiver can't archive messages.
+
+ >>> archivers['prototype'].archive_message(mlist, msg)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError
=== modified file 'mailman/interfaces/archiver.py'
--- a/mailman/interfaces/archiver.py 2008-01-13 21:17:38 +0000
+++ b/mailman/interfaces/archiver.py 2008-07-03 02:29:20 +0000
@@ -17,21 +17,32 @@
"""Interface for archiving schemes."""
+__metaclass__ = type
+__all__ = [
+ 'IArchiver',
+ 'IPipermailMailingList',
+ ]
+
from zope.interface import Interface, Attribute
+from mailman.interfaces.mailinglist import IMailingList
class IArchiver(Interface):
"""An interface to the archiver."""
- def get_list_url(mlist):
+ name = Attribute('The name of this archiver')
+
+ is_enabled = Attribute('True if this archiver is enabled.')
+
+ def list_url(mlist):
"""Return the url to the top of the list's archive.
:param mlist: The IMailingList object.
:returns: The url string.
"""
- def get_message_url(mlist, message):
+ def permalink(mlist, message):
"""Return the url to the message in the archive.
This url points directly to the message in the archive. This method
@@ -46,9 +57,6 @@
def archive_message(mlist, message):
"""Send the message to the archiver.
- This uses `get_message_url()` to calculate and return the url to the
- message in the archives.
-
:param mlist: The IMailingList object.
:param message: The message object.
:returns: The url string or None if the message's archive url cannot
@@ -56,3 +64,14 @@
"""
# XXX How to handle attachments?
+
+
+
+class IPipermailMailingList(IMailingList):
+ """An interface that adapts IMailingList as needed for Pipermail."""
+
+ def archive_dir():
+ """The directory for storing Pipermail artifacts.
+
+ Pipermail expects this to be a function, not a property.
+ """
=== modified file 'mailman/pipeline/cook_headers.py'
--- a/mailman/pipeline/cook_headers.py 2008-03-31 18:12:04 +0000
+++ b/mailman/pipeline/cook_headers.py 2008-07-03 02:29:20 +0000
@@ -30,7 +30,7 @@
from zope.interface import implements
from mailman import Utils
-from mailman.app.archiving import get_primary_archiver
+from mailman.app.plugins import get_plugins
from mailman.configuration import config
from mailman.i18n import _
from mailman.interfaces import IHandler, Personalization, ReplyToMunging
@@ -206,29 +206,26 @@
'List-Unsubscribe': subfieldfmt % (listinfo, mlist.leave_address),
'List-Subscribe' : subfieldfmt % (listinfo, mlist.join_address),
})
- archiver = get_primary_archiver(mlist)
if msgdata.get('reduced_list_headers'):
headers['X-List-Administrivia'] = 'yes'
else:
# List-Post: is controlled by a separate attribute
if mlist.include_list_post_header:
headers['List-Post'] = '<mailto:%s>' % mlist.posting_address
- # Add this header if we're archiving
+ # Add RFC 2369 and 5064 archiving headers, if archiving is enabled.
if mlist.archive:
- archiveurl = archiver.get_list_url()
- headers['List-Archive'] = '<%s>' % archiveurl
+ for archiver in get_plugins('mailman.archiver'):
+ if not archiver.is_enabled:
+ continue
+ headers['List-Archive'] = '<%s>' % archiver.list_url(mlist)
+ permalink = archiver.permalink(mlist, msg)
+ if permalink is not None:
+ headers['Archived-At'] = permalink
# XXX RFC 2369 also defines a List-Owner header which we are not currently
# supporting, but should.
- #
- # Draft RFC 5064 defines an Archived-At header which contains the pointer
- # directly to the message in the archive. If the currently defined
- # archiver can tell us the URL, go ahead and include this header.
- archived_at = archiver.get_message_url(msg)
- if archived_at is not None:
- headers['Archived-At'] = archived_at
- # First we delete any pre-existing headers because the RFC permits only
- # one copy of each, and we want to be sure it's ours.
for h, v in headers.items():
+ # First we delete any pre-existing headers because the RFC permits
+ # only one copy of each, and we want to be sure it's ours.
del msg[h]
# Wrap these lines if they are too long. 78 character width probably
# shouldn't be hardcoded, but is at least text-MUA friendly. The
=== modified file 'mailman/pipeline/docs/archives.txt'
--- a/mailman/pipeline/docs/archives.txt 2008-02-27 06:26:18 +0000
+++ b/mailman/pipeline/docs/archives.txt 2008-06-14 20:02:01 +0000
@@ -7,11 +7,11 @@
archivers to work in a separate process from the main Mailman delivery
processes.
+ >>> from mailman.app.lifecycle import create_list
+ >>> from mailman.configuration import config
>>> from mailman.queue import Switchboard
- >>> from mailman.configuration import config
>>> handler = config.handlers['to-archive']
- >>> mlist = config.db.list_manager.create(u'[EMAIL PROTECTED]')
- >>> mlist.preferred_language = u'en'
+ >>> mlist = create_list(u'[EMAIL PROTECTED]')
>>> switchboard = Switchboard(config.ARCHQUEUE_DIR)
A helper function.
=== modified file 'mailman/pipeline/docs/cook-headers.txt'
--- a/mailman/pipeline/docs/cook-headers.txt 2008-03-31 19:06:24 +0000
+++ b/mailman/pipeline/docs/cook-headers.txt 2008-07-03 02:29:20 +0000
@@ -186,6 +186,7 @@
>>> mlist.preferred_language = u'en'
>>> msg = message_from_string("""\
... From: [EMAIL PROTECTED]
+ ... Message-ID: <12345>
...
... """)
>>> process(mlist, msg, {})
=== modified file 'mailman/pipeline/scrubber.py'
--- a/mailman/pipeline/scrubber.py 2008-03-31 17:04:23 +0000
+++ b/mailman/pipeline/scrubber.py 2008-06-14 20:02:01 +0000
@@ -40,7 +40,7 @@
from mailman import Utils
from mailman.Errors import DiscardMessage
-from mailman.app.archiving import get_primary_archiver
+from mailman.app.plugins import get_plugin
from mailman.configuration import config
from mailman.i18n import _
from mailman.interfaces import IHandler
@@ -497,7 +497,7 @@
fp.write(decodedpayload)
fp.close()
# Now calculate the url to the list's archive.
- baseurl = get_primary_archiver(mlist).get_list_url()
+ baseurl = get_plugin('mailman.scrubber').list_url(mlist)
if not baseurl.endswith('/'):
baseurl += '/'
# Trailing space will definitely be a problem with format=flowed.
=== modified file 'mailman/queue/archive.py'
--- a/mailman/queue/archive.py 2008-03-31 17:16:40 +0000
+++ b/mailman/queue/archive.py 2008-06-15 04:01:55 +0000
@@ -80,6 +80,5 @@
# While a list archiving lock is acquired, archive the message.
with Lock(os.path.join(mlist.data_path, 'archive.lck')):
for archive_factory in get_plugins('mailman.archiver'):
- archiver = archive_factory(mlist)
- archiver.archive_message(msg)
+ archive_factory().archive_message(mlist, msg)
=== modified file 'setup.py'
--- a/setup.py 2008-04-26 05:39:14 +0000
+++ b/setup.py 2008-07-03 02:29:20 +0000
@@ -90,7 +90,11 @@
entry_points = {
'console_scripts': list(scripts),
# Entry point for plugging in different database backends.
- 'mailman.archiver' : 'default = mailman.app.archiving:Pipermail',
+ 'mailman.archiver' : [
+ 'pipermail = mailman.app.archiving:Pipermail',
+ 'prototype = mailman.app.archiving:Prototype',
+ ],
+ 'mailman.scrubber' : 'stock = mailman.app.archiving:Pipermail',
'mailman.commands' : list(commands),
'mailman.database' : 'stock = mailman.database:StockDatabase',
'mailman.mta' : 'stock = mailman.MTA:Manual',
--
Primary development focus
https://code.launchpad.net/~mailman-coders/mailman/3.0
You are receiving this branch notification because you are subscribed to it.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe:
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org