diff options
| author | Barry Warsaw | 2008-03-30 00:06:07 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2008-03-30 00:06:07 -0400 |
| commit | 7f440dc39489b32257c35f15ee6f27d90a197cf5 (patch) | |
| tree | a4aaec013ee63253b78cfeb3518e78b1df424a91 | |
| parent | eecf4b29f2642f30b22ee978fa50d8713bec1a48 (diff) | |
| download | mailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.tar.gz mailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.tar.zst mailman-7f440dc39489b32257c35f15ee6f27d90a197cf5.zip | |
| -rw-r--r-- | mailman/Archiver/Archiver.py | 23 | ||||
| -rw-r--r-- | mailman/Archiver/HyperArch.py | 57 | ||||
| -rw-r--r-- | mailman/Archiver/pipermail.py | 8 | ||||
| -rw-r--r-- | mailman/Defaults.py | 2 | ||||
| -rw-r--r-- | mailman/app/archiving.py | 79 | ||||
| -rw-r--r-- | mailman/bin/master.py | 2 | ||||
| -rw-r--r-- | mailman/pipeline/cook_headers.py | 9 | ||||
| -rw-r--r-- | mailman/pipeline/scrubber.py | 4 | ||||
| -rw-r--r-- | mailman/pipeline/to_digest.py | 2 | ||||
| -rw-r--r-- | mailman/queue/archive.py | 12 | ||||
| -rw-r--r-- | mailman/queue/docs/archiver.txt | 35 | ||||
| -rw-r--r-- | mailman/queue/outgoing.py | 6 | ||||
| -rw-r--r-- | mailman/rules/administrivia.py | 2 | ||||
| -rw-r--r-- | setup.py | 2 |
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 @@ -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', |
