summaryrefslogtreecommitdiff
path: root/Mailman/bin
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/bin')
-rw-r--r--Mailman/bin/bumpdigests.py73
-rwxr-xr-xMailman/bin/checkdbs.py181
-rw-r--r--Mailman/bin/disabled.py200
-rwxr-xr-xMailman/bin/gate_news.py243
-rw-r--r--Mailman/bin/nightly_gzip.py126
-rwxr-xr-xMailman/bin/senddigests.py71
6 files changed, 894 insertions, 0 deletions
diff --git a/Mailman/bin/bumpdigests.py b/Mailman/bin/bumpdigests.py
new file mode 100644
index 000000000..fe63410d6
--- /dev/null
+++ b/Mailman/bin/bumpdigests.py
@@ -0,0 +1,73 @@
+# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import sys
+import optparse
+
+from Mailman import Errors
+from Mailman import MailList
+from Mailman import Utils
+from Mailman import mm_cfg
+from Mailman.i18n import _
+
+# Work around known problems with some RedHat cron daemons
+import signal
+signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+__i18n_templates__ = True
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options] [listname ...]
+
+Increment the digest volume number and reset the digest number to one. All
+the lists named on the command line are bumped. If no list names are given,
+all lists are bumped."""))
+ opts, args = parser.parse_args()
+ return opts, args, parser
+
+
+
+def main():
+ opts, args, parser = parseargs()
+
+ listnames = set(args or Utils.list_names())
+ if not listnames:
+ print _('Nothing to do.')
+ sys.exit(0)
+
+ for listname in listnames:
+ try:
+ # Be sure the list is locked
+ mlist = MailList.MailList(listname)
+ except Errors.MMListError, e:
+ parser.print_help()
+ print >> sys.stderr, _('No such list: $listname')
+ sys.exit(1)
+ try:
+ mlist.bump_digest_volume()
+ finally:
+ mlist.Save()
+ mlist.Unlock()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/checkdbs.py b/Mailman/bin/checkdbs.py
new file mode 100755
index 000000000..ebcbdb177
--- /dev/null
+++ b/Mailman/bin/checkdbs.py
@@ -0,0 +1,181 @@
+# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import sys
+import time
+import optparse
+
+from email.Charset import Charset
+
+from Mailman import MailList
+from Mailman import Message
+from Mailman import Utils
+from Mailman import i18n
+from Mailman import mm_cfg
+
+_ = i18n._
+i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+
+__i18n_templates__ = True
+
+# Work around known problems with some RedHat cron daemons
+import signal
+signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+NL = u'\n'
+now = time.time()
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options]
+
+Check for pending admin requests and mail the list owners if necessary."""))
+ opts, args = parser.parse_args()
+ if args:
+ parser.print_help()
+ print >> sys.stderr, _('Unexpected arguments')
+ sys.exit(1)
+ return opts, args, parser
+
+
+
+def pending_requests(mlist):
+ # Must return a byte string
+ lcset = Utils.GetCharSet(mlist.preferred_language)
+ pending = []
+ first = True
+ for id in mlist.GetSubscriptionIds():
+ if first:
+ pending.append(_('Pending subscriptions:'))
+ first = False
+ when, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
+ if fullname:
+ if isinstance(fullname, unicode):
+ fullname = fullname.encode(lcset, 'replace')
+ fullname = ' (%s)' % fullname
+ pending.append(' %s%s %s' % (addr, fullname, time.ctime(when)))
+ first = True
+ for id in mlist.GetHeldMessageIds():
+ if first:
+ pending.append(_('\nPending posts:'))
+ first = False
+ info = mlist.GetRecord(id)
+ when, sender, subject, reason, text, msgdata = mlist.GetRecord(id)
+ subject = Utils.oneline(subject, lcset)
+ date = time.ctime(when)
+ reason = _(reason)
+ pending.append(_("""\
+From: $sender on $date
+Subject: $subject
+Cause: $reason"""))
+ pending.append('')
+ # Coerce all items in pending to a Unicode so we can join them
+ upending = []
+ charset = Utils.GetCharSet(mlist.preferred_language)
+ for s in pending:
+ if isinstance(s, unicode):
+ upending.append(s)
+ else:
+ upending.append(unicode(s, charset, 'replace'))
+ # Make sure that the text we return from here can be encoded to a byte
+ # string in the charset of the list's language. This could fail if for
+ # example, the request was pended while the list's language was French,
+ # but then it was changed to English before checkdbs ran.
+ text = NL.join(upending)
+ charset = Charset(Utils.GetCharSet(mlist.preferred_language))
+ incodec = charset.input_codec or 'ascii'
+ outcodec = charset.output_codec or 'ascii'
+ if isinstance(text, unicode):
+ return text.encode(outcodec, 'replace')
+ # Be sure this is a byte string encodeable in the list's charset
+ utext = unicode(text, incodec, 'replace')
+ return utext.encode(outcodec, 'replace')
+
+
+
+def auto_discard(mlist):
+ # Discard old held messages
+ discard_count = 0
+ expire = mm_cfg.days(mlist.max_days_to_hold)
+ heldmsgs = mlist.GetHeldMessageIds()
+ if expire and heldmsgs:
+ for id in heldmsgs:
+ if now - mlist.GetRecord(id)[0] > expire:
+ mlist.HandleRequest(id, mm_cfg.DISCARD)
+ discard_count += 1
+ mlist.Save()
+ return discard_count
+
+
+
+def main():
+ opts, args, parser = parseargs()
+
+ for name in Utils.list_names():
+ # The list must be locked in order to open the requests database
+ mlist = MailList.MailList(name)
+ try:
+ count = mlist.NumRequestsPending()
+ # While we're at it, let's evict yesterday's autoresponse data
+ midnight_today = Utils.midnight()
+ evictions = []
+ for sender in mlist.hold_and_cmd_autoresponses.keys():
+ date, respcount = mlist.hold_and_cmd_autoresponses[sender]
+ if Utils.midnight(date) < midnight_today:
+ evictions.append(sender)
+ if evictions:
+ for sender in evictions:
+ del mlist.hold_and_cmd_autoresponses[sender]
+ # This is the only place we've changed the list's database
+ mlist.Save()
+ if count:
+ i18n.set_language(mlist.preferred_language)
+ realname = mlist.real_name
+ discarded = auto_discard(mlist)
+ if discarded:
+ count = count - discarded
+ text = _(
+ 'Notice: $discarded old request(s) automatically expired.\n\n')
+ else:
+ text = ''
+ if count:
+ text += Utils.maketext(
+ 'checkdbs.txt',
+ {'count' : count,
+ 'host_name': mlist.host_name,
+ 'adminDB' : mlist.GetScriptURL('admindb', absolute=1),
+ 'real_name': realname,
+ }, mlist=mlist)
+ text += '\n' + pending_requests(mlist)
+ subject = _('$count $realname moderator request(s) waiting')
+ else:
+ subject = _('$realname moderator request check result')
+ msg = Message.UserNotification(mlist.GetOwnerEmail(),
+ mlist.GetBouncesEmail(),
+ subject, text,
+ mlist.preferred_language)
+ msg.send(mlist, **{'tomoderators': True})
+ finally:
+ mlist.Unlock()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/disabled.py b/Mailman/bin/disabled.py
new file mode 100644
index 000000000..161a06712
--- /dev/null
+++ b/Mailman/bin/disabled.py
@@ -0,0 +1,200 @@
+# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import sys
+import time
+import logging
+import optparse
+
+from Mailman import Errors
+from Mailman import MailList
+from Mailman import MemberAdaptor
+from Mailman import Pending
+from Mailman import Utils
+from Mailman import loginit
+from Mailman import mm_cfg
+from Mailman.Bouncer import _BounceInfo
+from Mailman.i18n import _
+
+__i18n_templates__ = True
+
+# Work around known problems with some RedHat cron daemons
+import signal
+signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+loginit.initialize(propagate=True)
+elog = logging.getLogger('mailman.error')
+blog = logging.getLogger('mailman.bounce')
+
+ALL = (MemberAdaptor.BYBOUNCE,
+ MemberAdaptor.BYADMIN,
+ MemberAdaptor.BYUSER,
+ MemberAdaptor.UNKNOWN,
+ )
+
+
+
+def who_callback(option, opt, value, parser):
+ dest = getattr(parser.values, option.dest)
+ if opt in ('-o', '--byadmin'):
+ dest.add(MemberAdaptor.BYADMIN)
+ elif opt in ('-m', '--byuser'):
+ dest.add(MemberAdaptor.BYUSER)
+ elif opt in ('-u', '--unknown'):
+ dest.add(MemberAdaptor.UNKNOWN)
+ elif opt in ('-b', '--notbybounce'):
+ dest.discard(MemberAdaptor.BYBOUNCE)
+ elif opt in ('-a', '--all'):
+ dest.update(ALL)
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options]
+
+Process disabled members, recommended once per day.
+
+This script iterates through every mailing list looking for members whose
+delivery is disabled. If they have been disabled due to bounces, they will
+receive another notification, or they may be removed if they've received the
+maximum number of notifications.
+
+Use the --byadmin, --byuser, and --unknown flags to also send notifications to
+members whose accounts have been disabled for those reasons. Use --all to
+send the notification to all disabled members."""))
+ # This is the set of working flags for who to send notifications to. By
+ # default, we notify anybody who has been disable due to bounces.
+ parser.set_defaults(who=set([MemberAdaptor.BYBOUNCE]))
+ parser.add_option('-o', '--byadmin',
+ callback=who_callback, action='callback', dest='who',
+ help=_("""\
+Also send notifications to any member disabled by the list
+owner/administrator."""))
+ parser.add_option('-m', '--byuser',
+ callback=who_callback, action='callback', dest='who',
+ help=_("""\
+Also send notifications to any member who has disabled themself."""))
+ parser.add_option('-u', '--unknown',
+ callback=who_callback, action='callback', dest='who',
+ help=_("""\
+Also send notifications to any member disabled for unknown reasons
+(usually a legacy disabled address)."""))
+ parser.add_option('-b', '--notbybounce',
+ callback=who_callback, action='callback', dest='who',
+ help=_("""\
+Don't send notifications to members disabled because of bounces (the
+default is to notify bounce disabled members)."""))
+ parser.add_option('-a', '--all',
+ callback=who_callback, action='callback', dest='who',
+ help=_('Send notifications to all disabled members'))
+ parser.add_option('-f', '--force',
+ default=False, action='store_true',
+ help=_("""\
+Send notifications to disabled members even if they're not due a new
+notification yet."""))
+ parser.add_option('-l', '--listname',
+ dest='listnames', action='append', default=[],
+ type='string', help=_("""\
+Process only the given list, otherwise do all lists."""))
+ opts, args = parser.parse_args()
+ return opts, args, parser
+
+
+
+def main():
+ opts, args, parser = parseargs()
+
+ listnames = set(opts.listnames or Utils.list_names())
+ who = tuple(opts.who)
+
+ msg = _('[disabled by periodic sweep and cull, no message available]')
+ today = time.mktime(time.localtime()[:3] + (0,) * 6)
+ for listname in listnames:
+ # List of members to notify
+ notify = []
+ mlist = MailList.MailList(listname)
+ try:
+ interval = mlist.bounce_you_are_disabled_warnings_interval
+ # Find all the members who are currently bouncing and see if
+ # they've reached the disable threshold but haven't yet been
+ # disabled. This is a sweep through the membership catching
+ # situations where they've bounced a bunch, then the list admin
+ # lowered the threshold, but we haven't (yet) seen more bounces
+ # from the member. Note: we won't worry about stale information
+ # or anything else since the normal bounce processing code will
+ # handle that.
+ disables = []
+ for member in mlist.getBouncingMembers():
+ if mlist.getDeliveryStatus(member) <> MemberAdaptor.ENABLED:
+ continue
+ info = mlist.getBounceInfo(member)
+ if info.score >= mlist.bounce_score_threshold:
+ disables.append((member, info))
+ if disables:
+ for member, info in disables:
+ mlist.disableBouncingMember(member, info, msg)
+ # Go through all the members who have delivery disabled, and find
+ # those that are due to have another notification. If they are
+ # disabled for another reason than bouncing, and we're processing
+ # them (because of the command line switch) then they won't have a
+ # bounce info record. We can piggyback on that for all disable
+ # purposes.
+ members = mlist.getDeliveryStatusMembers(who)
+ for member in members:
+ info = mlist.getBounceInfo(member)
+ if not info:
+ # See if they are bounce disabled, or disabled for some
+ # other reason.
+ status = mlist.getDeliveryStatus(member)
+ if status == MemberAdaptor.BYBOUNCE:
+ elog.error(
+ '%s disabled BYBOUNCE lacks bounce info, list: %s',
+ member, mlist.internal_name())
+ continue
+ info = _BounceInfo(
+ member, 0, today,
+ mlist.bounce_you_are_disabled_warnings,
+ mlist.pend_new(Pending.RE_ENABLE,
+ mlist.internal_name(),
+ member))
+ mlist.setBounceInfo(member, info)
+ lastnotice = time.mktime(info.lastnotice + (0,) * 6)
+ if opts.force or today >= lastnotice + interval:
+ notify.append(member)
+ # Now, send notifications to anyone who is due
+ for member in notify:
+ blog.info('Notifying disabled member %s for list: %s',
+ member, mlist.internal_name())
+ try:
+ mlist.sendNextNotification(member)
+ except Errors.NotAMemberError:
+ # There must have been some problem with the data we have
+ # on this member. Most likely it's that they don't have a
+ # password assigned. Log this and delete the member.
+ blog.info(
+ 'NotAMemberError when sending disabled notice: %s',
+ member)
+ mlist.ApprovedDeleteMember(member, 'cron/disabled')
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/gate_news.py b/Mailman/bin/gate_news.py
new file mode 100755
index 000000000..4c55273cd
--- /dev/null
+++ b/Mailman/bin/gate_news.py
@@ -0,0 +1,243 @@
+# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import os
+import sys
+import time
+import socket
+import logging
+import nntplib
+import optparse
+
+import email.Errors
+from email.Parser import Parser
+
+from Mailman import LockFile
+from Mailman import MailList
+from Mailman import Message
+from Mailman import Utils
+from Mailman import loginit
+from Mailman import mm_cfg
+from Mailman.Queue.sbcache import get_switchboard
+from Mailman.i18n import _
+
+# Work around known problems with some RedHat cron daemons
+import signal
+signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+GATENEWS_LOCK_FILE = os.path.join(mm_cfg.LOCK_DIR, 'gate_news.lock')
+
+LOCK_LIFETIME = mm_cfg.hours(2)
+NL = '\n'
+
+loginit.initialize(propagate=True)
+log = logging.getLogger('mailman.fromusenet')
+
+class _ContinueLoop(Exception):
+ pass
+
+__i18n_templates__ = True
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options]
+
+Poll the NNTP servers for messages to be gatewayed to mailing lists."""))
+ opts, args = parser.parse_args()
+ if args:
+ parser.print_help()
+ print >> sys.stderr, _('Unexpected arguments')
+ sys.exit(1)
+ return opts, args, parser
+
+
+
+_hostcache = {}
+
+def open_newsgroup(mlist):
+ # Split host:port if given
+ nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host)
+ # Open up a "mode reader" connection to nntp server. This will be shared
+ # for all the gated lists having the same nntp_host.
+ conn = _hostcache.get(mlist.nntp_host)
+ if conn is None:
+ try:
+ conn = nntplib.NNTP(nntp_host, nntp_port,
+ readermode=True,
+ user=mm_cfg.NNTP_USERNAME,
+ password=mm_cfg.NNTP_PASSWORD)
+ except (socket.error, nntplib.NNTPError, IOError), e:
+ log.error('error opening connection to nntp_host: %s\n%s',
+ mlist.nntp_host, e)
+ raise
+ _hostcache[mlist.nntp_host] = conn
+ # Get the GROUP information for the list, but we're only really interested
+ # in the first article number and the last article number
+ r, c, f, l, n = conn.group(mlist.linked_newsgroup)
+ return conn, int(f), int(l)
+
+
+def clearcache():
+ for conn in set(_hostcache.values()):
+ conn.quit()
+ _hostcache.clear()
+
+
+
+# This function requires the list to be locked.
+def poll_newsgroup(mlist, conn, first, last, glock):
+ listname = mlist.internal_name()
+ # NEWNEWS is not portable and has synchronization issues.
+ for num in range(first, last):
+ glock.refresh()
+ try:
+ headers = conn.head(repr(num))[3]
+ found_to = False
+ beenthere = False
+ for header in headers:
+ i = header.find(':')
+ value = header[:i].lower()
+ if i > 0 and value == 'to':
+ found_to = True
+ if value <> 'x-beenthere':
+ continue
+ if header[i:] == ': %s' % mlist.GetListEmail():
+ beenthere = True
+ break
+ if not beenthere:
+ body = conn.body(repr(num))[3]
+ # Usenet originated messages will not have a Unix envelope
+ # (i.e. "From " header). This breaks Pipermail archiving, so
+ # we will synthesize one. Be sure to use the format searched
+ # for by mailbox.UnixMailbox._isrealfromline(). BAW: We use
+ # the -bounces address here in case any downstream clients use
+ # the envelope sender for bounces; I'm not sure about this,
+ # but it's the closest to the old semantics.
+ lines = ['From %s %s' % (mlist.GetBouncesEmail(),
+ time.ctime(time.time()))]
+ lines.extend(headers)
+ lines.append('')
+ lines.extend(body)
+ lines.append('')
+ p = Parser(Message.Message)
+ try:
+ msg = p.parsestr(NL.join(lines))
+ except email.Errors.MessageError, e:
+ log.error('email package exception for %s:%d\n%s',
+ mlist.linked_newsgroup, num, e)
+ raise _ContinueLoop
+ if found_to:
+ del msg['X-Originally-To']
+ msg['X-Originally-To'] = msg['To']
+ del msg['To']
+ msg['To'] = mlist.GetListEmail()
+ # Post the message to the locked list
+ inq = get_switchboard(mm_cfg.INQUEUE_DIR)
+ inq.enqueue(msg,
+ listname=mlist.internal_name(),
+ fromusenet=True)
+ log.info('posted to list %s: %7d', listname, num)
+ except nntplib.NNTPError, e:
+ log.exception('NNTP error for list %s: %7d', listname, num)
+ except _ContinueLoop:
+ continue
+ # Even if we don't post the message because it was seen on the
+ # list already, update the watermark
+ mlist.usenet_watermark = num
+
+
+
+def process_lists(glock):
+ for listname in Utils.list_names():
+ glock.refresh()
+ # Open the list unlocked just to check to see if it is gating news to
+ # mail. If not, we're done with the list. Otherwise, lock the list
+ # and gate the group.
+ mlist = MailList.MailList(listname, lock=False)
+ if not mlist.gateway_to_mail:
+ continue
+ # Get the list's watermark, i.e. the last article number that we gated
+ # from news to mail. None means that this list has never polled its
+ # newsgroup and that we should do a catch up.
+ watermark = getattr(mlist, 'usenet_watermark', None)
+ # Open the newsgroup, but let most exceptions percolate up.
+ try:
+ conn, first, last = open_newsgroup(mlist)
+ except (socket.error, nntplib.NNTPError):
+ break
+ log.info('%s: [%d..%d]', listname, first, last)
+ try:
+ try:
+ if watermark is None:
+ mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
+ # This is the first time we've tried to gate this
+ # newsgroup. We essentially do a mass catch-up, otherwise
+ # we'd flood the mailing list.
+ mlist.usenet_watermark = last
+ log.info('%s caught up to article %d', listname, last)
+ else:
+ # The list has been polled previously, so now we simply
+ # grab all the messages on the newsgroup that have not
+ # been seen by the mailing list. The first such article
+ # is the maximum of the lowest article available in the
+ # newsgroup and the watermark. It's possible that some
+ # articles have been expired since the last time gate_news
+ # has run. Not much we can do about that.
+ start = max(watermark + 1, first)
+ if start > last:
+ log.info('nothing new for list %s', listname)
+ else:
+ mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
+ log.info('gating %s articles [%d..%d]',
+ listname, start, last)
+ # Use last+1 because poll_newsgroup() employes a for
+ # loop over range, and this will not include the last
+ # element in the list.
+ poll_newsgroup(mlist, conn, start, last + 1, glock)
+ except LockFile.TimeOutError:
+ log.error('Could not acquire list lock: %s', listname)
+ finally:
+ if mlist.Locked():
+ mlist.Save()
+ mlist.Unlock()
+ log.info('%s watermark: %d', listname, mlist.usenet_watermark)
+
+
+
+def main():
+ opts, args, parser = parseargs()
+ lock = LockFile.LockFile(GATENEWS_LOCK_FILE,
+ # It's okay to hijack this
+ lifetime=LOCK_LIFETIME)
+ try:
+ lock.lock(timeout=0.5)
+ except LockFile.TimeOutError:
+ log.error('Could not acquire gate_news lock')
+ return
+ try:
+ process_lists(lock)
+ finally:
+ clearcache()
+ lock.unlock(unconditionally=True)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/nightly_gzip.py b/Mailman/bin/nightly_gzip.py
new file mode 100644
index 000000000..037612adb
--- /dev/null
+++ b/Mailman/bin/nightly_gzip.py
@@ -0,0 +1,126 @@
+#! @PYTHON@
+#
+# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import os
+import sys
+import optparse
+
+try:
+ import gzip
+except ImportError:
+ sys.exit(0)
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import MailList
+from Mailman.i18n import _
+
+__i18n_templates__ = True
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options] [listname ...]
+
+Re-generate the Pipermail gzip'd archive flat files."""))
+ parser.add_option('-v', '--verbose',
+ default=False, action='store_true',
+ help=_("Print each file as it's being gzip'd"))
+ parser.add_option('-z', '--level',
+ default=6, type='int',
+ help=_('Specifies the compression level'))
+ opts, args = parser.parse_args()
+ if opts.level < 1 or opts.level > 9:
+ parser.print_help()
+ print >> sys.stderr, _('Illegal compression level: $opts.level')
+ sys.exit(1)
+ return opts, args, parser
+
+
+
+def compress(txtfile, opts):
+ if opts.verbose:
+ print _("gzip'ing: $txtfile")
+ infp = outfp = None
+ try:
+ infp = open(txtfile)
+ outfp = gzip.open(txtfile + '.gz', 'wb', opts.level)
+ outfp.write(infp.read())
+ finally:
+ if outfp:
+ outfp.close()
+ if infp:
+ infp.close()
+
+
+
+def main():
+ if mm_cfg.ARCHIVE_TO_MBOX not in (1, 2) or mm_cfg.GZIP_ARCHIVE_TXT_FILES:
+ # We're only going to run the nightly archiver if messages are
+ # archived to the mbox, and the gzip file is not created on demand
+ # (i.e. for every individual post). This is the normal mode of
+ # operation.
+ return
+
+ opts, args, parser = parseargs()
+
+ # Process all the specified lists
+ for listname in set(args or Utils.list_names()):
+ mlist = MailList.MailList(listname, lock=False)
+ if not mlist.archive:
+ continue
+ dir = mlist.archive_dir()
+ try:
+ allfiles = os.listdir(dir)
+ except OSError:
+ # Has the list received any messages? If not, last_post_time will
+ # be zero, so it's not really a bogus archive dir.
+ if mlist.last_post_time > 0:
+ print _('List $listname has a bogus archive_directory: $dir')
+ continue
+ if opts.verbose:
+ print _('Processing list: $listname')
+ files = []
+ for f in allfiles:
+ if os.path.splitext(f)[1] <> '.txt':
+ continue
+ # stat both the .txt and .txt.gz files and append them only if
+ # the former is newer than the latter.
+ txtfile = os.path.join(dir, f)
+ gzpfile = txtfile + '.gz'
+ txt_mtime = os.path.getmtime(txtfile)
+ try:
+ gzp_mtime = os.path.getmtime(gzpfile)
+ except OSError:
+ gzp_mtime = -1
+ if txt_mtime > gzp_mtime:
+ files.append(txtfile)
+ for f in files:
+ compress(f, opts)
+
+
+
+if __name__ == '__main__':
+ omask = os.umask(002)
+ try:
+ main()
+ finally:
+ os.umask(omask)
diff --git a/Mailman/bin/senddigests.py b/Mailman/bin/senddigests.py
new file mode 100755
index 000000000..fa01d3666
--- /dev/null
+++ b/Mailman/bin/senddigests.py
@@ -0,0 +1,71 @@
+# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import sys
+import optparse
+
+from Mailman import MailList
+from Mailman import Utils
+from Mailman import mm_cfg
+from Mailman.i18n import _
+
+# Work around known problems with some RedHat cron daemons
+import signal
+signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+__i18n_templates__ = True
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ usage=_("""\
+%prog [options]
+
+Dispatch digests for lists w/pending messages and digest_send_periodic
+set."""))
+ parser.add_option('-l', '--listname',
+ type='string', default=[], action='append',
+ dest='listnames', help=_("""\
+Send the digest for the given list only, otherwise the digests for all
+lists are sent out. Multiple -l options may be given."""))
+ opts, args = parser.parse_args()
+ if args:
+ parser.print_help()
+ print >> sys.stderr, _('Unexpected arguments')
+ sys.exit(1)
+ return opts, args, parser
+
+
+
+def main():
+ opts, args, parser = parseargs()
+
+ for listname in set(opts.listnames or Utils.list_names()):
+ mlist = MailList.MailList(listname, lock=False)
+ if mlist.digest_send_periodic:
+ mlist.Lock()
+ try:
+ mlist.send_digest_now()
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+
+
+
+if __name__ == '__main__':
+ main()