summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2008-03-30 00:06:07 -0400
committerBarry Warsaw2008-03-30 00:06:07 -0400
commit7f440dc39489b32257c35f15ee6f27d90a197cf5 (patch)
treea4aaec013ee63253b78cfeb3518e78b1df424a91
parenteecf4b29f2642f30b22ee978fa50d8713bec1a48 (diff)
downloadmailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.tar.gz
mailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.tar.zst
mailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.zip
-rw-r--r--mailman/Archiver/Archiver.py23
-rw-r--r--mailman/Archiver/HyperArch.py57
-rw-r--r--mailman/Archiver/pipermail.py8
-rw-r--r--mailman/Defaults.py2
-rw-r--r--mailman/app/archiving.py79
-rw-r--r--mailman/bin/master.py2
-rw-r--r--mailman/pipeline/cook_headers.py9
-rw-r--r--mailman/pipeline/scrubber.py4
-rw-r--r--mailman/pipeline/to_digest.py2
-rw-r--r--mailman/queue/archive.py12
-rw-r--r--mailman/queue/docs/archiver.txt35
-rw-r--r--mailman/queue/outgoing.py6
-rw-r--r--mailman/rules/administrivia.py2
-rw-r--r--setup.py2
14 files changed, 146 insertions, 97 deletions
diff --git a/mailman/Archiver/Archiver.py b/mailman/Archiver/Archiver.py
index 3ffa11972..ebe7c35e7 100644
--- a/mailman/Archiver/Archiver.py
+++ b/mailman/Archiver/Archiver.py
@@ -31,12 +31,11 @@ import traceback
from cStringIO import StringIO
from string import Template
-from Mailman import Mailbox
-from Mailman import Utils
-from Mailman.SafeDict import SafeDict
-from Mailman.configuration import config
-from Mailman.configuration import config
-from Mailman.i18n import _
+from mailman import Mailbox
+from mailman import Utils
+from mailman.SafeDict import SafeDict
+from mailman.configuration import config
+from mailman.i18n import _
log = logging.getLogger('mailman.error')
@@ -82,10 +81,11 @@ class Archiver:
# the private directory, pointing directly to the private/listname
# which has o+rx permissions. Private archives do not have the
# symbolic links.
+ archdir = archive_dir(self.fqdn_listname)
omask = os.umask(0)
try:
try:
- os.mkdir(self.archive_dir()+'.mbox', 02775)
+ os.mkdir(archdir+'.mbox', 02775)
except OSError, e:
if e.errno <> errno.EEXIST: raise
# We also create an empty pipermail archive directory into
@@ -93,12 +93,12 @@ class Archiver:
# that lists that have not yet received a posting have
# /something/ as their index.html, and don't just get a 404.
try:
- os.mkdir(self.archive_dir(), 02775)
+ os.mkdir(archdir, 02775)
except OSError, e:
if e.errno <> errno.EEXIST: raise
# See if there's an index.html file there already and if not,
# write in the empty archive notice.
- indexfile = os.path.join(self.archive_dir(), 'index.html')
+ indexfile = os.path.join(archdir, 'index.html')
fp = None
try:
fp = open(indexfile)
@@ -119,11 +119,6 @@ class Archiver:
finally:
os.umask(omask)
- def archive_dir(self):
- # Return the private archive directory
- return os.path.join(config.PRIVATE_ARCHIVE_FILE_DIR,
- self.fqdn_listname)
-
def ArchiveFileName(self):
"""The mbox name where messages are left for archive construction."""
return os.path.join(self.archive_dir() + '.mbox',
diff --git a/mailman/Archiver/HyperArch.py b/mailman/Archiver/HyperArch.py
index 0d81d2652..9c0bdd3f9 100644
--- a/mailman/Archiver/HyperArch.py
+++ b/mailman/Archiver/HyperArch.py
@@ -43,15 +43,14 @@ from email.Errors import HeaderParseError
from email.Header import decode_header, make_header
from locknix.lockfile import Lock
-from Mailman import Errors
-from Mailman import MailList
-from Mailman import Utils
-from Mailman import i18n
-from Mailman.Archiver import HyperDatabase
-from Mailman.Archiver import pipermail
-from Mailman.Mailbox import ArchiverMailbox
-from Mailman.SafeDict import SafeDict
-from Mailman.configuration import config
+from mailman import Errors
+from mailman import Utils
+from mailman import i18n
+from mailman.Archiver import HyperDatabase
+from mailman.Archiver import pipermail
+from mailman.Mailbox import ArchiverMailbox
+from mailman.SafeDict import SafeDict
+from mailman.configuration import config
log = logging.getLogger('mailman.error')
@@ -303,30 +302,6 @@ class Article(pipermail.Article):
self.decode_headers()
- # Mapping of listnames to MailList instances as a weak value dictionary.
- # This code is copied from Runner.py but there's one important operational
- # difference. In Runner.py, we always .Load() the MailList object for
- # each _dispose() run, otherwise the object retrieved from the cache won't
- # be up-to-date. Since we're creating a new HyperArchive instance for
- # each message being archived, we don't need to worry about that -- but it
- # does mean there are additional opportunities for optimization.
- _listcache = weakref.WeakValueDictionary()
-
- def _open_list(self, listname):
- # Cache the open list so that any use of the list within this process
- # uses the same object. We use a WeakValueDictionary so that when the
- # list is no longer necessary, its memory is freed.
- mlist = self._listcache.get(listname)
- if not mlist:
- try:
- mlist = MailList.MailList(listname, lock=0)
- except Errors.MMListError, e:
- log.error('error opening list: %s\n%s', listname, e)
- return None
- else:
- self._listcache[listname] = mlist
- return mlist
-
def __getstate__(self):
d = self.__dict__.copy()
# We definitely don't want to pickle the MailList instance, so just
@@ -355,7 +330,7 @@ class Article(pipermail.Article):
listname = d.get('__listname')
if listname:
del d['__listname']
- d['_mlist'] = self._open_list(listname)
+ d['_mlist'] = config.db.list_manager.get(listname)
if not d.has_key('_lang'):
if hasattr(self, '_mlist'):
self._lang = self._mlist.preferred_language
@@ -394,7 +369,7 @@ class Article(pipermail.Article):
if subject:
if config.ARCHIVER_OBSCURES_EMAILADDRS:
with i18n.using_language(self._lang):
- atmark = unicode(_(' at '), Utils.GetCharSet(self._lang))
+ atmark = _(' at ')
subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
'\g<1>' + atmark + '\g<2>', subject)
self.decoded['subject'] = subject
@@ -443,7 +418,7 @@ class Article(pipermail.Article):
if config.ARCHIVER_OBSCURES_EMAILADDRS:
# Point the mailto url back to the list
author = re.sub('@', _(' at '), self.author)
- emailurl = self._mlist.GetListEmail()
+ emailurl = self._mlist.posting_address
else:
author = self.author
emailurl = self.email
@@ -451,7 +426,7 @@ class Article(pipermail.Article):
d["email_url"] = url_quote(emailurl)
d["datestr_html"] = self.quote(i18n.ctime(int(self.date)))
d["body"] = self._get_body()
- d['listurl'] = self._mlist.GetScriptURL('listinfo', absolute=1)
+ d['listurl'] = self._mlist.script_url('listinfo')
d['listname'] = self._mlist.real_name
d['encoding'] = ''
charset = Utils.GetCharSet(self._lang)
@@ -546,7 +521,7 @@ class Article(pipermail.Article):
body = unicode(body, cset, 'replace')
if config.ARCHIVER_OBSCURES_EMAILADDRS:
with i18n.using_language(self._lang):
- atmark = unicode(_(' at '), cset)
+ atmark = _(' at ')
body = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
'\g<1>' + atmark + '\g<2>', body)
# Return body to character set of article.
@@ -650,7 +625,7 @@ class HyperArchive(pipermail.T):
with i18n.using_language(mlist.preferred_language):
d = {"lastdate": quotetime(self.lastdate),
"archivedate": quotetime(self.archivedate),
- "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
+ "listinfo": mlist.script_url('listinfo'),
"version": self.version,
}
i = {"thread": _("thread"),
@@ -679,7 +654,7 @@ class HyperArchive(pipermail.T):
d = {"listname": html_quote(mlist.real_name, self.lang),
"archtype": self.type,
"archive": self.volNameToDesc(self.archive),
- "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
+ "listinfo": mlist.script_url('listinfo'),
"firstdate": quotetime(self.firstdate),
"lastdate": quotetime(self.lastdate),
"size": self.size,
@@ -710,7 +685,7 @@ class HyperArchive(pipermail.T):
listname = mlist.fqdn_listname
mbox = os.path.join(mlist.archive_dir()+'.mbox', listname+'.mbox')
d = {"listname": mlist.real_name,
- "listinfo": mlist.GetScriptURL('listinfo', absolute=1),
+ "listinfo": mlist.script_url('listinfo'),
"fullarch": '../%s.mbox/%s.mbox' % (listname, listname),
"size": sizeof(mbox, mlist.preferred_language),
'meta': '',
diff --git a/mailman/Archiver/pipermail.py b/mailman/Archiver/pipermail.py
index 1483b5c97..a7c4e3a98 100644
--- a/mailman/Archiver/pipermail.py
+++ b/mailman/Archiver/pipermail.py
@@ -13,13 +13,13 @@ from cStringIO import StringIO
from email.Utils import parseaddr, parsedate_tz, mktime_tz, formatdate
from string import lowercase
-__version__ = '0.10 (Mailman edition)'
+__version__ = '0.11 (Mailman edition)'
VERSION = __version__
CACHESIZE = 100 # Number of slots in the cache
-from Mailman import Errors
-from Mailman.Mailbox import ArchiverMailbox
-from Mailman.i18n import _
+from mailman import Errors
+from mailman.Mailbox import ArchiverMailbox
+from mailman.i18n import _
SPACE = ' '
diff --git a/mailman/Defaults.py b/mailman/Defaults.py
index 51ff8a039..04152dd33 100644
--- a/mailman/Defaults.py
+++ b/mailman/Defaults.py
@@ -273,7 +273,7 @@ PRIVATE_EXTERNAL_ARCHIVER = No
# a MailList object and a Message object. It should raise
# Errors.DiscardMessage if it wants to throw the message away. Otherwise it
# should modify the Message object as necessary.
-ARCHIVE_SCRUBBER = 'mailman.Handlers.Scrubber'
+ARCHIVE_SCRUBBER = 'mailman.pipeline.scrubber'
# Control parameter whether mailman.Handlers.Scrubber should use message
# attachment's filename as is indicated by the filename parameter or use
diff --git a/mailman/app/archiving.py b/mailman/app/archiving.py
index cd1d7ca8e..c790bc3dc 100644
--- a/mailman/app/archiving.py
+++ b/mailman/app/archiving.py
@@ -17,21 +17,46 @@
"""Application level archiving support."""
+__metaclass__ = type
__all__ = [
'Pipermail',
- 'get_archiver',
+ 'get_primary_archiver',
]
-__metaclass__ = type
+import os
+import pkg_resources
+
from string import Template
from zope.interface import implements
from zope.interface.verify import verifyObject
-from mailman.app.plugins import get_plugin
+from mailman.app.plugins import get_plugins
from mailman.configuration import config
from mailman.interfaces import IArchiver
+from mailman.Archiver.HyperArch import HyperArchive
+from cStringIO import StringIO
+
+
+
+class PipermailMailingListAdapter:
+ """An adapter for MailingList objects to work with Pipermail."""
+
+ def __init__(self, mlist):
+ self._mlist = mlist
+
+ def __getattr__(self, name):
+ return getattr(self._mlist, name)
+
+ def archive_dir(self):
+ """The directory for storing Pipermail artifacts."""
+ if self._mlist.archive_private:
+ basedir = config.PRIVATE_ARCHIVE_FILE_DIR
+ else:
+ basedir = config.PUBLIC_ARCHIVE_FILE_DIR
+ return os.path.join(basedir, self._mlist.fqdn_listname)
+
class Pipermail:
@@ -39,39 +64,47 @@ class Pipermail:
implements(IArchiver)
- def get_list_url(self, mlist):
+ def __init__(self, mlist):
+ self._mlist = mlist
+
+ def get_list_url(self):
"""See `IArchiver`."""
- if mlist.archive_private:
- url = mlist.script_url('private') + '/index.html'
+ if self._mlist.archive_private:
+ url = self._mlist.script_url('private') + '/index.html'
else:
- web_host = config.domains.get(mlist.host_name, mlist.host_name)
+ web_host = config.domains.get(
+ self._mlist.host_name, self._mlist.host_name)
url = Template(config.PUBLIC_ARCHIVE_URL).safe_substitute(
- listname=mlist.fqdn_listname,
+ listname=self._mlist.fqdn_listname,
hostname=web_host,
- fqdn_listname=mlist.fqdn_listname,
+ fqdn_listname=self._mlist.fqdn_listname,
)
return url
- def get_message_url(self, mlist, message):
+ def get_message_url(self, message):
"""See `IArchiver`."""
# Not currently implemented.
return None
- def archive_message(self, mlist, message):
+ def archive_message(self, message):
"""See `IArchiver`."""
+ text = str(message)
+ fileobj = StringIO(text)
+ h = HyperArchive(PipermailMailingListAdapter(self._mlist))
+ h.processUnixMailbox(fileobj)
+ h.close()
+ fileobj.close()
+ # There's no good way to know the url for the archived message.
return None
-_archiver = None
-
-def get_archiver():
- """Return the currently registered global archiver.
-
- Uses the plugin architecture to find the IArchiver instance.
- """
- global _archiver
- if _archiver is None:
- _archiver = get_plugin('mailman.archiver')()
- verifyObject(IArchiver, _archiver)
- return _archiver
+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
diff --git a/mailman/bin/master.py b/mailman/bin/master.py
index e033d4d77..6337764ef 100644
--- a/mailman/bin/master.py
+++ b/mailman/bin/master.py
@@ -213,7 +213,7 @@ Lock file: $config.LOCK_FILE
Lock host: $hostname
Exiting.""")
- parser.error(message)
+ config.options.parser.error(message)
diff --git a/mailman/pipeline/cook_headers.py b/mailman/pipeline/cook_headers.py
index c5e21da5f..2cf156b2c 100644
--- a/mailman/pipeline/cook_headers.py
+++ b/mailman/pipeline/cook_headers.py
@@ -31,10 +31,11 @@ from zope.interface import implements
from mailman import Utils
from mailman import Version
-from mailman.app.archiving import get_archiver
from mailman.configuration import config
from mailman.i18n import _
from mailman.interfaces import IHandler, Personalization, ReplyToMunging
+from mailman.app.archiving import get_primary_archiver
+
CONTINUATION = ',\n\t'
COMMASPACE = ', '
@@ -205,7 +206,7 @@ def process(mlist, msg, msgdata):
'List-Unsubscribe': subfieldfmt % (listinfo, mlist.leave_address),
'List-Subscribe' : subfieldfmt % (listinfo, mlist.join_address),
})
- archiver = get_archiver()
+ archiver = get_primary_archiver(mlist)
if msgdata.get('reduced_list_headers'):
headers['X-List-Administrivia'] = 'yes'
else:
@@ -214,7 +215,7 @@ def process(mlist, msg, msgdata):
headers['List-Post'] = '<mailto:%s>' % mlist.posting_address
# Add this header if we're archiving
if mlist.archive:
- archiveurl = archiver.get_list_url(mlist)
+ archiveurl = archiver.get_list_url()
headers['List-Archive'] = '<%s>' % archiveurl
# XXX RFC 2369 also defines a List-Owner header which we are not currently
# supporting, but should.
@@ -222,7 +223,7 @@ def process(mlist, msg, msgdata):
# 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(mlist, msg)
+ 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
diff --git a/mailman/pipeline/scrubber.py b/mailman/pipeline/scrubber.py
index 37a5c7873..394fe6322 100644
--- a/mailman/pipeline/scrubber.py
+++ b/mailman/pipeline/scrubber.py
@@ -40,7 +40,7 @@ from zope.interface import implements
from mailman import Utils
from mailman.Errors import DiscardMessage
-from mailman.app.archiving import get_archiver
+from mailman.app.archiving import get_primary_archiver
from mailman.configuration import config
from mailman.i18n import _
from mailman.interfaces import IHandler
@@ -497,7 +497,7 @@ def save_attachment(mlist, msg, dir, filter_html=True):
fp.write(decodedpayload)
fp.close()
# Now calculate the url to the list's archive.
- baseurl = get_archiver().get_list_url(mlist)
+ baseurl = get_primary_archiver().get_list_url(mlist)
if not baseurl.endswith('/'):
baseurl += '/'
# Trailing space will definitely be a problem with format=flowed.
diff --git a/mailman/pipeline/to_digest.py b/mailman/pipeline/to_digest.py
index 05b1dc3d5..628fea65d 100644
--- a/mailman/pipeline/to_digest.py
+++ b/mailman/pipeline/to_digest.py
@@ -347,7 +347,7 @@ def send_i18n_digests(mlist, mboxfp):
print >> plainmsg
# Now add the footer
if mlist.digest_footer:
- footertxt = decorate(mlist, mlist.digest_footer, _('digest footer'))
+ footertxt = decorate(mlist, mlist.digest_footer)
# MIME
footer = MIMEText(footertxt.encode(lcset), _charset=lcset)
footer['Content-Description'] = _('Digest Footer')
diff --git a/mailman/queue/archive.py b/mailman/queue/archive.py
index 47627a04e..854e4340f 100644
--- a/mailman/queue/archive.py
+++ b/mailman/queue/archive.py
@@ -19,12 +19,19 @@
from __future__ import with_statement
+__metaclass__ = type
+__all__ = [
+ 'ArchiveRunner',
+ ]
+
+
import os
import time
from email.Utils import parsedate_tz, mktime_tz, formatdate
from locknix.lockfile import Lock
+from mailman.app.plugins import get_plugins
from mailman.configuration import config
from mailman.queue import Runner
@@ -70,4 +77,7 @@ class ArchiveRunner(Runner):
msg['X-List-Received-Date'] = receivedtime
# While a list archiving lock is acquired, archive the message.
with Lock(os.path.join(mlist.data_path, 'archive.lck')):
- mlist.ArchiveMail(msg)
+ for archive_factory in get_plugins('mailman.archiver'):
+ archiver = archive_factory(mlist)
+ archiver.archive_message(msg)
+
diff --git a/mailman/queue/docs/archiver.txt b/mailman/queue/docs/archiver.txt
new file mode 100644
index 000000000..43b6d4974
--- /dev/null
+++ b/mailman/queue/docs/archiver.txt
@@ -0,0 +1,35 @@
+Archiving
+=========
+
+Mailman can archive to any number of archivers that adhere to the IArchiver
+interface. By default, there's a Pipermail archiver.
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist = create_list(u'test@example.com')
+ >>> mlist.web_page_url = u'http://www.example.com/'
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ... To: test@example.com
+ ... Subject: My first post
+ ... Message-ID: <first>
+ ...
+ ... First post!
+ ... """)
+
+ >>> from mailman.configuration import config
+ >>> from mailman.queue import Switchboard
+ >>> archiver_queue = Switchboard(config.ARCHQUEUE_DIR)
+ >>> ignore = archiver_queue.enqueue(msg, {}, listname=mlist.fqdn_listname)
+
+ >>> from mailman.queue.archive import ArchiveRunner
+ >>> from mailman.tests.helpers import make_testable_runner
+ >>> runner = make_testable_runner(ArchiveRunner)
+ >>> runner.run()
+
+ # The best we can do is verify some landmark exists. Let's use the
+ # Pipermail pickle file exists.
+ >>> import os
+ >>> os.path.exists(os.path.join(config.PUBLIC_ARCHIVE_FILE_DIR,
+ ... mlist.fqdn_listname, 'pipermail.pck'))
+ True
diff --git a/mailman/queue/outgoing.py b/mailman/queue/outgoing.py
index 838d7d137..4c067b8c0 100644
--- a/mailman/queue/outgoing.py
+++ b/mailman/queue/outgoing.py
@@ -20,10 +20,10 @@
import os
import sys
import copy
-import time
import email
import socket
import logging
+import datetime
from mailman import Errors
from mailman import Message
@@ -57,7 +57,7 @@ class OutgoingRunner(Runner, BounceMixin):
def _dispose(self, mlist, msg, msgdata):
# See if we should retry delivery of this message again.
deliver_after = msgdata.get('deliver_after', 0)
- if time.time() < deliver_after:
+ if datetime.datetime.now() < deliver_after:
return True
# Make sure we have the most up-to-date state
try:
@@ -102,7 +102,7 @@ class OutgoingRunner(Runner, BounceMixin):
# occasionally move them back here for another shot at
# delivery.
if e.tempfailures:
- now = time.time()
+ now = datetime.datetime.now()
recips = e.tempfailures
last_recip_count = msgdata.get('last_recip_count', 0)
deliver_until = msgdata.get('deliver_until', now)
diff --git a/mailman/rules/administrivia.py b/mailman/rules/administrivia.py
index 4de4fc61a..26fe1d9b8 100644
--- a/mailman/rules/administrivia.py
+++ b/mailman/rules/administrivia.py
@@ -64,7 +64,7 @@ class Administrivia:
return False
# First check the Subject text.
lines_to_check = []
- subject = msg.get('subject', '')
+ subject = str(msg.get('subject', ''))
if subject <> '':
lines_to_check.append(subject)
# Search only the first text/plain subpart of the message. There's
diff --git a/setup.py b/setup.py
index aa2a75939..b3327c038 100644
--- a/setup.py
+++ b/setup.py
@@ -83,7 +83,7 @@ Any other spelling is incorrect.""",
entry_points = {
'console_scripts': list(scripts),
# Entry point for plugging in different database backends.
- 'mailman.archiver' : 'stock = mailman.app.archiving:Pipermail',
+ 'mailman.archiver' : 'default = mailman.app.archiving:Pipermail',
'mailman.database' : 'stock = mailman.database:StockDatabase',
'mailman.mta' : 'stock = mailman.MTA:Manual',
'mailman.styles' : 'default = mailman.app.styles:DefaultStyle',