File mailman-2.1.14-editarch.patch of Package mailman
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -471,6 +471,8 @@ def show_results(mlist, doc, category, s
_('Go to the general list information page')))
otherlinks.AddItem(Link(mlist.GetScriptURL('edithtml'),
_('Edit the public HTML pages and text files')))
+ otherlinks.AddItem(Link(mlist.GetScriptURL('editarch'),
+ _('Edit the list archives')))
otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(),
_('Go to list archives')).Format() +
'<br> <br>')
--- /dev/null
+++ b/Mailman/Cgi/editarch.py
@@ -0,0 +1,509 @@
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998,1999,2000,2001,2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Script which implements admin editing of the list's archives."""
+
+import os
+import cgi
+import errno
+import signal
+import mailbox
+import time
+import uu
+
+from email.Header import decode_header, make_header
+from email.Utils import _bdecode, _qdecode, parsedate_tz
+
+from Mailman import Utils
+from Mailman import MailList
+from Mailman.htmlformat import *
+from Mailman import Errors
+from Mailman.Cgi import Auth
+from Mailman.Logging.Syslog import syslog
+from Mailman import i18n
+from Mailman import mm_cfg
+from Mailman.Mailbox import ArchiverMailbox
+from Mailman import LockFile
+
+_ = i18n._
+
+
+
+def main():
+ _ = i18n._
+ doc = Document()
+
+ # Set up the system default language
+ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+
+ parts = Utils.GetPathPieces()
+ if not parts:
+ doc.AddItem(Header(2, _("List name is required.")))
+ print doc.Format()
+ return
+
+ listname = parts[0].lower()
+ try:
+ mlist = MailList.MailList(listname, lock=0)
+ except Errors.MMListError, e:
+ # Avoid cross-site scripting attacks
+ safelistname = Utils.websafe(listname)
+ doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>')))
+ print doc.Format()
+ syslog('error', 'No such list "%s": %s', listname, e)
+ return
+
+ # Now that we have a valid list, set the language to its default
+ i18n.set_language(mlist.preferred_language)
+ doc.set_language(mlist.preferred_language)
+
+ # Must be authenticated to get any farther
+ cgidata = cgi.FieldStorage()
+
+ # Editing the archives for a list is limited to the list admin and
+ # site admin.
+ if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
+ mm_cfg.AuthSiteAdmin),
+ cgidata.getvalue('adminpw', '')):
+ if cgidata.has_key('admlogin'):
+ # This is a re-authorization attempt
+ msg = Bold(FontSize('+1', _('Authorization failed.'))).Format()
+ else:
+ msg = ''
+ Auth.loginpage(mlist, 'admin', msg=msg)
+ return
+
+ realname = mlist.real_name
+ name = mlist.ArchiveFileName()
+ wname = name + '.working'
+
+ try:
+ os.stat(name)
+ except (IOError, os.error):
+ # no archive file
+ doc.AddItem(Header(1, _('%(realname)s')))
+ doc.AddItem(_('There are no archives to edit.'))
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ return
+
+ # Open archive file
+ archfile = open(name)
+ mbox = mailbox.UnixMailbox(archfile)
+
+ doc.SetTitle(_('%(realname)s -- Edit the list archives'))
+
+ # If there are multiple parts, display a particular message or month
+ if len(parts) > 1:
+ # Make sure that parts[2] and up have good int values
+ i = 2
+ while i < len(parts):
+ try:
+ parts[i] = int(parts[i])
+ except ValueError:
+ parts[i] = 0
+ i += 1
+
+ if parts[1] == 'box' and len(parts) == 4:
+ datestr = MakeDateString(parts[2], parts[3])
+ doc.AddItem(Header(1, _('%(realname)s Archives -- ' + datestr)))
+ DisplayBox(mlist, mbox, doc, parts[2], parts[3])
+ elif parts[1] == 'message' and len(parts) == 3 and parts[2] > 0:
+ msg_num = parts[2]
+ doc.AddItem(Header(1, _('%(realname)s Archives -- Message')))
+ doc.AddItem('<hr>\n')
+ l = Link(mlist.GetScriptURL('editarch') + '/confirm/' + str(msg_num), _('Delete this message'))
+ doc.AddItem('<div align="center">')
+ doc.AddItem(l)
+ doc.AddItem('</div>')
+
+ doc.AddItem('\n<hr>\n')
+ DisplayMessage(mlist, mbox, msg_num, doc, 0)
+ elif parts[1] == 'confirm' and len(parts) == 3 and parts[2] > 0:
+ msg_num = parts[2]
+ doc.AddItem(Header(1, _('%(realname)s Archives -- Delete Message?')))
+ doc.AddItem('<hr>\n')
+ l = Link(mlist.GetScriptURL('editarch') + '/delete/' + str(msg_num), _('Confirm delete'))
+ doc.AddItem('<div align="center">')
+ doc.AddItem(l)
+ doc.AddItem('\n<br>\n')
+ doc.AddItem(_('(This may take a few seconds. Please be patient.)'))
+ doc.AddItem('\n</div>')
+
+ doc.AddItem('<hr>\n')
+ DisplayMessage(mlist, mbox, msg_num, doc, 1)
+ elif parts[1] == 'delete' and len(parts) == 3 and parts[2] > 0:
+ msg_num = parts[2]
+ DeleteMessage(mlist, mbox, msg_num, wname, doc)
+ os.rename(wname, name)
+ TagForArchProcessing(mlist)
+
+ print 'Content-type: text/html\n\n'
+ print '<meta http-equiv="Refresh" content="0; url=' + mlist.GetScriptURL('editarch') + '/deleted/' + str(msg_num) + '">'
+ l = Link(mlist.GetScriptURL('editarch') + '/deleted/' + str(msg_num), _('Click here to continue'))
+ print '<html><body>' + l.Format() + '</body></html>'
+ return
+
+ elif parts[1] == 'deleted' and len(parts) == 3 and parts[2] > 0:
+ msg_num = parts[2]
+ doc.AddItem(Header(1, _('%(realname)s Archives -- Message Deleted')))
+ doc.AddItem(_('<strong><em>Important:</em></strong> It\'s best to use the links below to continue editing. If you do go back, be sure to refresh/reload to be sure you are deleting the right message!\n<p>\n'))
+ doc.AddItem('<p>\n<hr>\n<p>\n')
+ doc.AddItem(_('Continue editing:\n<p>\n'))
+ # if there is a previous message, display the link
+ if msg_num > 1:
+ l = Link(mlist.GetScriptURL('editarch') + '/message/' + str(msg_num - 1), _('<-- Previous Message'))
+ doc.AddItem(l)
+ doc.AddItem(' ')
+ # the next message will have shifted to the current number
+ l = Link(mlist.GetScriptURL('editarch') + '/message/' + str(msg_num), _('Next Message -->'))
+ doc.AddItem(l)
+
+ doc.AddItem('<p>\n')
+ date = GetMessageDate(mbox, msg_num)
+ if date is not None:
+ l = Link(mlist.GetScriptURL('editarch') + '/box/' + str(date[0]) + '/' + str(date[1]), _('Back to ') + MakeDateString(date[0], date[1]))
+ doc.AddItem(l)
+ doc.AddItem('<p>\n')
+ l = Link(mlist.GetScriptURL('editarch'), _('Go to main overview'))
+ doc.AddItem(l)
+ else:
+ doc.AddItem(Header(1, _('%(realname)s Archives')))
+ doc.AddItem('<p>\n<hr>\n<p>\n')
+ l = Link(mlist.GetScriptURL('editarch'), _('Go to main overview'))
+ doc.AddItem(l)
+ # If just the listname, display the default overview
+ else:
+ doc.AddItem(Header(1, _('%(realname)s Archives -- Overview')))
+ l = Link(mlist.GetBaseArchiveURL(), _('list archives'))
+ doc.AddItem(_('Your changes will show up immediately here, but they will not show up immediately in the normal '))
+ doc.AddItem(l)
+ doc.AddItem(_('. Archives will be reprocessed nightly, so check back tomorrow to see the changes.\n<p>\nMessages with non-standard date stamps will show up in the current month, so be sure to look there if you\'re having trouble finding a message.'))
+ DisplayMonthOverview(mlist, mbox, doc)
+
+ archfile.close()
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+
+ return
+
+# Display a single message by message number
+def DisplayMessage(mlist, mbox, msg_num, doc, deleting):
+ counter = 1
+ while 1:
+ m = mbox.next()
+ if m is None:
+ doc.AddItem(_('No messages matched.') + '\n<p>\n')
+ l = Link(mlist.GetScriptURL('editarch'), _('Go to main overview'))
+ doc.AddItem(l)
+ break
+ if counter == msg_num:
+ body = decode_body(m, m.fp.read())
+ header_date = m.get('date')
+ header_from = decode(m.get('from'))
+ header_subject = decode(m.get('subject'))
+ doc.AddItem('<pre>\n')
+ doc.AddItem(_('<b>Date:</b> ') + header_date + '\n')
+ doc.AddItem(_('<b>From:</b> ') + html_quote(header_from) + '\n')
+ doc.AddItem(_('<b>Subject:</b> ') + html_quote(header_subject) + '\n\n')
+ doc.AddItem(html_quote(body) + '\n\n')
+ doc.AddItem('</pre>\n')
+ doc.AddItem('<p>\n')
+ if not deleting:
+ doc.AddItem('<p><hr><p>\n')
+ if counter > 0:
+ l = Link(mlist.GetScriptURL('editarch') + '/message/' + str(counter - 1), _('<-- Previous Message'))
+ doc.AddItem(l)
+ doc.AddItem(' ')
+ l = Link(mlist.GetScriptURL('editarch') + '/message/' + str(counter + 1), _('Next Message -->'))
+ doc.AddItem(l)
+ doc.AddItem('<p>\n')
+ date = GetDate(m)
+ l = Link(mlist.GetScriptURL('editarch') + '/box/' + str(date[0]) + '/' + str(date[1]), _('Back to ') + MakeDateString(date[0], date[1]))
+ doc.AddItem(l)
+ doc.AddItem('<p>\n')
+ l = Link(mlist.GetScriptURL('editarch'), _('Go to main overview'))
+ doc.AddItem(l)
+ break
+ counter += 1
+
+# Display a list of messages from the specified month/year
+def DisplayBox(mlist, mbox, doc, year, month):
+ counter = 1
+
+ doc.AddItem('<ul>\n')
+ while 1:
+ try:
+ m = mbox.next()
+ except Errors.DiscardMessage:
+ continue
+ if m is None:
+ break
+
+ date = GetDate(m)
+ thisyear = date[0]
+ thismonth = date[1]
+
+ if thismonth == month and thisyear == year:
+ header_subject = decode(m.get('subject', 'n/a'))
+ l = Link(mlist.GetScriptURL('editarch') + '/message/' + str(counter), html_quote(header_subject))
+ doc.AddItem('<li>')
+ doc.AddItem(l)
+ header_from = decode(m.getaddr('from')[0])
+ if header_from:
+ doc.AddItem(' <i>' + html_quote(header_from) + '</i>')
+ doc.AddItem('</li>\n')
+ counter += 1
+ doc.AddItem('</ul>\n')
+ doc.AddItem('<p>\n<hr>\n<p>\n')
+ l = Link(mlist.GetScriptURL('editarch'), _('Back to main overview'))
+ doc.AddItem(l)
+
+# Display list of months containing messages in the archive
+def DisplayMonthOverview(mlist, mbox, doc):
+ counter = 1
+ inc = 100
+ lastend = 0
+ firstcounter = counter
+ firstdate = time.localtime(0)
+ lastdate = time.localtime()
+ monthlist = [ ]
+ while 1:
+ try:
+ m = mbox.next()
+ except Errors.DiscardMessage:
+ continue
+ if m is None:
+ monthlist.append(lastdate[:2])
+ break
+
+ date = GetDate(m)
+
+ lastyear = lastdate[0]
+ lastmonth = lastdate[1]
+ thisyear = date[0]
+ thismonth = date[1]
+
+ if counter != 1 and thismonth != lastmonth or thisyear != lastyear:
+ monthlist.append(lastdate[:2])
+
+ counter += 1
+ lastdate = date
+
+ if counter == 1:
+ doc.AddItem(_('No messages in archive.\n<p>\n'))
+ return
+
+ monthlist.sort()
+ monthlist = RemoveDuplicates(monthlist)
+ monthlist.reverse()
+
+ links = UnorderedList()
+ for date in monthlist:
+ l = Link(mlist.GetScriptURL('editarch') + '/box/' + str(date[0]) + '/' + str(date[1]), MakeDateString(date[0], date[1]))
+ links.AddItem(l)
+ doc.AddItem(links)
+
+# Delete a message by message number, locking the list while doing so
+def DeleteMessage(mlist, mbox, msg_num, wname, doc):
+ # Unlocking method from Mailman/Cgi/admin.py
+ def sigterm_handler(signum, frame, mlist=mlist):
+ # Make sure the list gets unlocked...
+ mlist.Unlock()
+ # ...and ensure we exit, otherwise race conditions could cause us to
+ # enter MailList.Save() while we're in the unlocked state, and that
+ # could be bad!
+ sys.exit(0)
+
+ # Lock the list for good measure
+ omask = os.umask(002)
+ mlist.Lock()
+ try:
+ signal.signal(signal.SIGTERM, sigterm_handler)
+
+ # Lock the archives while working
+ lock_file = None
+ lock_file = LockFile.LockFile(
+ os.path.join(mm_cfg.LOCK_DIR,
+ mlist.internal_name() + '.archiver.lock'), lifetime=3*60)
+ try:
+ lock_file.lock(timeout=0.5)
+ except LockFile.AlreadyLockedError:
+ doc.AddItem('Couldn\'t lock the archives. Try again in a few minutes.')
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+ except LockFile.TimeOutError:
+ doc.AddItem('Couldn\'t lock the archives. Try again in a few minutes.')
+ doc.AddItem(mlist.GetMailmanFooter())
+ print doc.Format()
+ sys.exit(0)
+
+ warchfile = file(wname, 'w')
+
+ counter = 1
+ while 1:
+ try:
+ m = mbox.next()
+ except Errors.DiscardMessage:
+ continue
+ if m is None:
+ break
+ if counter != msg_num:
+ warchfile.write(m.unixfrom)
+ for l in m.headers:
+ warchfile.write(l)
+ warchfile.write('\n')
+ warchfile.write(m.fp.read())
+ counter += 1
+
+ if lock_file:
+ lock_file.unlock(unconditionally=1)
+
+ finally:
+ mlist.Unlock()
+ os.umask(omask)
+
+# Add the listname to the file of archives to be reprocessed
+def TagForArchProcessing(mlist):
+ fp = open(mm_cfg.EDITED_ARCHIVES_FILE, 'a')
+ fp.write(mlist.internal_name() + '\n')
+ fp.close()
+
+# Get the date of a message by message number
+def GetMessageDate(mbox, msg_num):
+ counter = 1
+ while 1:
+ m = mbox.next()
+ if m is None:
+ return None
+ if counter == msg_num:
+ return GetDate(m)
+ counter += 1
+ return
+
+# Get the date of a given message
+def GetDate(m):
+ date = floatdate('date', m)
+ if date is None:
+ date = floatdate('x-list-received-date', m)
+ if date is None:
+ date = time.localtime()
+
+ # if the year is two digits since the epoch, make it four
+ # (some messages show up in pipermail as being from 1969, so 68 is
+ # the cutoff)
+ if date[0] > 68 and date[0] < 100:
+ tempdatelist = list(date)
+ tempdatelist[0] += 1900
+ date = tuple(tempdatelist)
+
+ # if the year is still screwy, set the date to now
+ if date[0] < 1969:
+ date = time.localtime()
+
+ return date
+
+def floatdate(header, message):
+ missing = []
+ datestr = message.get(header, missing)
+ if datestr is missing:
+ return None
+ date = parsedate_tz(datestr)
+ return date
+
+# Make a human-readable Month YYYY from month and year numbers
+def MakeDateString(year, month):
+ try:
+ date = time.strptime(str(year) + " " + str(month), "%Y %m")
+ datestr = _(time.strftime("%B %Y", date))
+ return datestr
+ except ValueError:
+ return ''
+
+# Remove duplicates from a sorted list
+def RemoveDuplicates(list):
+ n = len(list)
+ last = list[0]
+ i = 1
+ lasti = 1
+ while i < n:
+ if list[i] != last:
+ list[lasti] = list[i]
+ last = list[i]
+ lasti += 1
+ i += 1
+ return list[:lasti]
+
+# body decoding adapted from email Message
+def decode_body(m, field):
+ cte = m.get('content-transfer-encoding', '').lower()
+ if cte == 'quoted-printable':
+ return _qdecode(field)
+ elif cte == 'base64':
+ try:
+ return _bdecode(field)
+ except binascii.Error:
+ # Incorrect padding
+ return field
+ elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
+ sfp = StringIO()
+ try:
+ uu.decode(StringIO(field+'\n'), sfp)
+ field = sfp.getvalue()
+ except uu.Error:
+ # Some decoding problem
+ return field
+ return field
+
+# header decoding adapted from Archiver/HyperArch.py
+def decode(field):
+ if field is None:
+ return field
+ decoded_field = decode_charset(field)
+ if decoded_field:
+ field = decoded_field
+ return field
+
+def decode_charset(field):
+ if field.find("=?") == -1:
+ return None
+ # Get the decoded header as a list of (s, charset) tuples
+ pairs = decode_header(field)
+ # Use __unicode__() until we can guarantee Python 2.2
+ try:
+ # Use a large number for maxlinelen so it won't get wrapped
+ h = make_header(pairs, 99999)
+ return h.__unicode__()
+ except (UnicodeError, LookupError):
+ # Unknown encoding
+ return None
+ # The last value for c will have the proper charset in it
+ return EMPTYSTRING.join([s for s, c in pairs])
+
+# from Archiver/HyperArch.py
+def html_quote(s, lang=None):
+ if s is None:
+ return ''
+ repls = ( ('&', '&'),
+ ("<", '<'),
+ (">", '>'),
+ ('"', '"'))
+ for thing, repl in repls:
+ s = s.replace(thing, repl)
+ return Utils.uncanonstr(s, lang)
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -1733,6 +1733,10 @@ PIDFILE = os.path.join(DATA_DIR, 'master
SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw')
LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw')
+# File which stores a list of recently edited archives.
+EDITED_ARCHIVES_FILE = os.path.join(DATA_DIR, 'edited-archives.txt')
+
+
# Import a bunch of version numbers
from Version import *
--- a/configure
+++ b/configure
@@ -4585,6 +4585,7 @@ build/cron/cull_bad_shunt:cron/cull_bad_
build/cron/disabled:cron/disabled \
build/cron/gate_news:cron/gate_news \
build/cron/mailpasswds:cron/mailpasswds \
+build/cron/nightly_archives:cron/nightly_archives \
build/cron/nightly_gzip:cron/nightly_gzip \
build/cron/senddigests:cron/senddigests \
"
--- a/configure.in
+++ b/configure.in
@@ -733,6 +733,7 @@ cron/cull_bad_shunt \
cron/disabled \
cron/gate_news \
cron/mailpasswds \
+cron/nightly_archives \
cron/nightly_gzip \
cron/senddigests \
])
--- a/cron/Makefile.in
+++ b/cron/Makefile.in
@@ -42,7 +42,7 @@ CRONDIR= $(prefix)/cron
SHELL= /bin/sh
PROGRAMS= checkdbs mailpasswds senddigests gate_news \
- nightly_gzip bumpdigests disabled cull_bad_shunt
+ nightly_gzip bumpdigests disabled cull_bad_shunt nightly_archives
FILES= crontab.in
BUILDDIR= ../build/cron
--- a/cron/crontab.in.in
+++ b/cron/crontab.in.in
@@ -25,3 +25,7 @@
#
# At 4:30AM daily, cull old entries from the 'bad' and 'shunt' queues.
30 4 * * * @PYTHON@ -S @prefix@/cron/cull_bad_shunt
+#
+# At 3:57am every night, reprocess archives that have been edited.
+57 3 * * * @PYTHON@ -S @prefix@/cron/nightly_archives
+
--- /dev/null
+++ b/cron/nightly_archives
@@ -0,0 +1,126 @@
+#! @PYTHON@
+#
+# Copyright (C) 1998,1999,2000,2001,2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+"""Redo the archives which have been edited. The files specified in
+mm_cfg.EDITED_ARCHIVES_FILE is used as input.
+
+This script should be run nightly from cron. When run from the command line,
+the following usage is understood:
+
+Usage: %(program)s [-v] [-h]
+
+Where:
+ --verbose
+ -v
+ print each list as its archives are being redone
+
+ --help
+ -h
+ print this message and exit
+
+"""
+
+import sys
+import os
+import time
+from stat import *
+from string import strip
+import getopt
+import paths
+from Mailman.i18n import _
+
+import paths
+# mm_cfg must be imported before the other modules, due to the side-effect of
+# it hacking sys.paths to include site-packages. Without this, running this
+# script from cron with python -S will fail.
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import MailList
+
+
+
+program = sys.argv[0]
+VERBOSE = 0
+
+def usage(code, msg=''):
+ if code:
+ fd = sys.stderr
+ else:
+ fd = sys.stdout
+ print >> fd, _(__doc__) % globals()
+ if msg:
+ print >> fd, msg
+ sys.exit(code)
+
+def main():
+ global VERBOSE
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'vh', ['verbose', 'help'])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ # defaults
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-v', '--verbose'):
+ VERBOSE = 1
+
+ # get listnames from edited-archives files
+ try:
+ fp = open(mm_cfg.EDITED_ARCHIVES_FILE)
+ listnames = fp.readlines()
+ fp.close()
+ except IOError:
+ if VERBOSE:
+ print 'No archives to process.'
+ return
+
+ listnames.sort()
+ listnames = RemoveDuplicates(listnames)
+ # process all the specified lists
+ for name in listnames:
+ name = strip(name)
+ mlist = MailList.MailList(name, lock=0)
+ if not mlist:
+ continue
+
+ if VERBOSE:
+ print 'Processing list: ' + name
+ # note: locking is taken care of in bin/arch
+ archcommand = os.path.join(mm_cfg.EXEC_PREFIX, 'bin/arch')
+ os.system(archcommand + ' -q --wipe ' + name)
+
+ # remove edited-archives file
+ os.unlink(mm_cfg.EDITED_ARCHIVES_FILE)
+
+def RemoveDuplicates(list):
+ n = len(list)
+ last = list[0]
+ i = 1
+ lasti = 1
+ while i < n:
+ if list[i] != last:
+ list[lasti] = list[i]
+ last = list[i]
+ lasti += 1
+ i += 1
+ return list[:lasti]
+
+if __name__ == '__main__':
+ main()
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -72,7 +72,7 @@ DIRSETGID= chmod g+s
# Fixed definitions
-CGI_PROGS= admindb admin confirm create edithtml listinfo options \
+CGI_PROGS= admindb admin confirm create editarch edithtml listinfo options \
private rmlist roster subscribe
COMMONOBJS= common.o vsnprintf.o