Expand wildcards in IMAP mailbox names

The only non-obvious code change required for this is due to the fact that
computing the archive names has to move into the format-specific archiving
functions, because they can no longer be derived from the mailbox name
beforehand.
This commit is contained in:
Nikolaus Schulz 2010-08-09 11:32:01 +02:00
parent 910b507a2d
commit f08403c99b
1 changed files with 129 additions and 123 deletions

View File

@ -1090,13 +1090,7 @@ def archive(mailbox_name):
set_signal_handlers() set_signal_handlers()
os.umask(077) # saves setting permissions on mailboxes/tempfiles os.umask(077) # saves setting permissions on mailboxes/tempfiles
final_archive_name = make_archive_name(mailbox_name) vprint("processing '%s'" % mailbox_name)
vprint("archiving '%s' to '%s' ..." % (mailbox_name, final_archive_name))
check_archive(final_archive_name)
dest_dir = os.path.dirname(final_archive_name)
if not dest_dir:
dest_dir = os.getcwd()
check_sane_destdir(dest_dir)
is_imap = urlparse.urlparse(mailbox_name)[0] in ('imap', 'imaps') is_imap = urlparse.urlparse(mailbox_name)[0] in ('imap', 'imaps')
if not is_imap: if not is_imap:
# Check if the mailbox exists, and refuse to mess with other people's # Check if the mailbox exists, and refuse to mess with other people's
@ -1121,19 +1115,19 @@ def archive(mailbox_name):
if is_imap: if is_imap:
vprint("guessing mailbox is of type: imap(s)") vprint("guessing mailbox is of type: imap(s)")
_archive_imap(mailbox_name, final_archive_name) _archive_imap(mailbox_name)
elif os.path.isfile(mailbox_name): elif os.path.isfile(mailbox_name):
vprint("guessing mailbox is of type: mbox") vprint("guessing mailbox is of type: mbox")
_archive_mbox(mailbox_name, final_archive_name) _archive_mbox(mailbox_name)
elif os.path.isdir(mailbox_name): elif os.path.isdir(mailbox_name):
cur_path = os.path.join(mailbox_name, "cur") cur_path = os.path.join(mailbox_name, "cur")
new_path = os.path.join(mailbox_name, "new") new_path = os.path.join(mailbox_name, "new")
if os.path.isdir(cur_path) and os.path.isdir(new_path): if os.path.isdir(cur_path) and os.path.isdir(new_path):
vprint("guessing mailbox is of type: maildir") vprint("guessing mailbox is of type: maildir")
_archive_dir(mailbox_name, final_archive_name, "maildir") _archive_dir(mailbox_name, "maildir")
else: else:
vprint("guessing mailbox is of type: MH") vprint("guessing mailbox is of type: MH")
_archive_dir(mailbox_name, final_archive_name, "mh") _archive_dir(mailbox_name, "mh")
else: else:
user_error("'%s' is not a normal file or directory" % mailbox_name) user_error("'%s' is not a normal file or directory" % mailbox_name)
@ -1145,17 +1139,12 @@ def archive(mailbox_name):
tempfile.tempdir = old_temp_dir tempfile.tempdir = old_temp_dir
clean_up() clean_up()
def _archive_mbox(mailbox_name, final_archive_name): def _archive_mbox(mailbox_name):
"""Archive a 'mbox' style mailbox - used by archive_mailbox() """Archive a 'mbox' style mailbox - used by archive_mailbox()"""
Arguments:
mailbox_name -- the filename/dirname of the mailbox to be archived
final_archive_name -- the filename of the 'mbox' mailbox to archive
old messages to - appending if the archive
already exists
"""
assert mailbox_name assert mailbox_name
assert final_archive_name final_archive_name = make_archive_name(mailbox_name)
vprint("archiving '%s' to '%s' ..." % (mailbox_name, final_archive_name))
check_archive(final_archive_name)
stats = Stats(mailbox_name, final_archive_name) stats = Stats(mailbox_name, final_archive_name)
cache = IdentityCache(mailbox_name) cache = IdentityCache(mailbox_name)
original = Mbox(path=mailbox_name) original = Mbox(path=mailbox_name)
@ -1234,11 +1223,13 @@ def _archive_mbox(mailbox_name, final_archive_name):
stats.display() stats.display()
def _archive_dir(mailbox_name, final_archive_name, type): def _archive_dir(mailbox_name, type):
"""Archive a 'maildir' or 'MH' style mailbox - used by archive_mailbox()""" """Archive a 'maildir' or 'MH' style mailbox - used by archive_mailbox()"""
assert mailbox_name assert mailbox_name
assert final_archive_name
assert type assert type
final_archive_name = make_archive_name(mailbox_name)
vprint("archiving '%s' to '%s' ..." % (mailbox_name, final_archive_name))
check_archive(final_archive_name)
stats = Stats(mailbox_name, final_archive_name) stats = Stats(mailbox_name, final_archive_name)
delete_queue = [] delete_queue = []
@ -1286,10 +1277,9 @@ def _archive_dir(mailbox_name, final_archive_name, type):
if not options.quiet: if not options.quiet:
stats.display() stats.display()
def _archive_imap(mailbox_name, final_archive_name): def _archive_imap(mailbox_name):
"""Archive an imap mailbox - used by archive_mailbox()""" """Archive an imap mailbox - used by archive_mailbox()"""
assert mailbox_name assert mailbox_name
assert final_archive_name
import imaplib import imaplib
import cStringIO import cStringIO
import getpass import getpass
@ -1297,10 +1287,8 @@ def _archive_imap(mailbox_name, final_archive_name):
vprint("Setting imaplib.Debug = %d" % options.debug_imap) vprint("Setting imaplib.Debug = %d" % options.debug_imap)
imaplib.Debug = options.debug_imap imaplib.Debug = options.debug_imap
archive = None archive = None
stats = Stats(mailbox_name, final_archive_name)
cache = IdentityCache(mailbox_name)
imap_str = mailbox_name[mailbox_name.find('://') + 3:] imap_str = mailbox_name[mailbox_name.find('://') + 3:]
imap_username, imap_password, imap_server, imap_folder = \ imap_username, imap_password, imap_server, imap_folder_pattern = \
parse_imap_url(imap_str) parse_imap_url(imap_str)
if not imap_password: if not imap_password:
if options.pwfile: if options.pwfile:
@ -1327,6 +1315,16 @@ def _archive_imap(mailbox_name, final_archive_name):
user_error("imap server %s has login disabled (hint: " user_error("imap server %s has login disabled (hint: "
"try ssl/imaps)" % imap_server) "try ssl/imaps)" % imap_server)
mailboxes = imap_find_mailboxes(imap_srv, imap_folder_pattern)
for imap_folder in mailboxes:
final_archive_name = make_archive_name(imap_folder)
vprint("archiving mailbox '%s' on IMAP server '%s' to '%s' ..." %
(imap_folder, imap_server, final_archive_name))
check_archive(final_archive_name)
cur_mailbox = mailbox_name[:-len(imap_folder_pattern)] + imap_folder
stats = Stats(cur_mailbox, final_archive_name)
cache = IdentityCache(cur_mailbox)
imap_smart_select(imap_srv, imap_folder) imap_smart_select(imap_srv, imap_folder)
total_msg_count = int(imap_srv.response("EXISTS")[1][0]) total_msg_count = int(imap_srv.response("EXISTS")[1][0])
vprint("folder has %d message(s)" % total_msg_count) vprint("folder has %d message(s)" % total_msg_count)
@ -1402,11 +1400,12 @@ def _archive_imap(mailbox_name, final_archive_name):
'+FLAGS.SILENT', '\\Deleted') '+FLAGS.SILENT', '\\Deleted')
if result != 'OK': unexpected_error("Error while deleting " if result != 'OK': unexpected_error("Error while deleting "
"messages; server says '%s'" % response[0]) "messages; server says '%s'" % response[0])
vprint("Closing mailbox and terminating connection.") vprint("Closing mailbox.")
imap_srv.close() imap_srv.close()
imap_srv.logout()
if not options.quiet: if not options.quiet:
stats.display() stats.display()
vprint("Terminating connection.")
imap_srv.logout()
############### IMAP functions ############### ############### IMAP functions ###############
@ -1497,9 +1496,7 @@ def imap_get_namespace(srv):
def imap_smart_select(srv, mailbox): def imap_smart_select(srv, mailbox):
"""Select the given mailbox on the IMAP server, correcting an invalid """Select the given mailbox on the IMAP server."""
mailbox path if possible."""
mailbox = imap_find_mailbox(srv, mailbox)
roflag = options.dry_run or options.copy_old_mail roflag = options.dry_run or options.copy_old_mail
# Work around python bug #1277098 (still pending in python << 2.5) # Work around python bug #1277098 (still pending in python << 2.5)
if not roflag: if not roflag:
@ -1528,10 +1525,13 @@ def imap_smart_select(srv, mailbox):
"upon SELECT") "upon SELECT")
def imap_find_mailbox(srv, mailbox): def imap_find_mailboxes(srv, mailbox):
"""Find the given mailbox on the IMAP server, correcting an invalid """Find matching mailboxes on the IMAP server, correcting an invalid
mailbox path if possible. Return the found mailbox name.""" mailbox path if possible."""
for curbox in imap_guess_mailboxnames(srv, mailbox): for curbox in imap_guess_mailboxnames(srv, mailbox):
if '%' in curbox or '*' in curbox:
vprint("Looking for mailboxes matching '%s'..." % curbox)
else:
vprint("Looking for mailbox '%s'..." % curbox) vprint("Looking for mailbox '%s'..." % curbox)
result, response = srv.list(pattern=curbox) result, response = srv.list(pattern=curbox)
if result != 'OK': if result != 'OK':
@ -1544,13 +1544,18 @@ def imap_find_mailbox(srv, mailbox):
break break
else: else:
user_error("Cannot find mailbox '%s' on server." % mailbox) user_error("Cannot find mailbox '%s' on server." % mailbox)
vprint("Found mailbox '%s'" % curbox) mailboxes = []
# Catch \NoSelect here to avoid misleading errors later. for mailbox_data in response:
m = re.match(r'\((?P<attrs>[^\)]*)\)', response[0]) m = re.match(r'\((.*?)\) "." "(.*?)"', mailbox_data)
if '\\noselect' in m.group('attrs').lower().split(): attrs, name = m.groups()
user_error("Server indicates that mailbox '%s' is not selectable" \ if '\\noselect' in attrs.lower().split():
% curbox) vprint("skipping not selectable mailbox '%s'" % name)
return curbox continue
vprint("Found mailbox '%s'" % name)
mailboxes.append(name)
if not mailboxes:
user_error("No matching folder is selectable")
return mailboxes
def imap_guess_mailboxnames(srv, mailbox): def imap_guess_mailboxnames(srv, mailbox):
@ -1656,10 +1661,6 @@ def make_archive_name(mailbox_name):
prefix = time.strftime(options.archive_prefix, tm) prefix = time.strftime(options.archive_prefix, tm)
if options.archive_suffix: if options.archive_suffix:
suffix = time.strftime(options.archive_suffix, tm) suffix = time.strftime(options.archive_suffix, tm)
if re.match(r'imaps?://', mailbox_name.lower()):
archive_head = ""
archive_tail = mailbox_name.rsplit('/', 1)[-1]
else:
archive_head, archive_tail = os.path.split(mailbox_name) archive_head, archive_tail = os.path.split(mailbox_name)
if not prefix: if not prefix:
# Don't create hidden archives, e.g. when processing Maildir++ # Don't create hidden archives, e.g. when processing Maildir++
@ -1680,7 +1681,8 @@ def check_sane_destdir(dir):
user_error("no write permission on output directory: '%s'" % dir) user_error("no write permission on output directory: '%s'" % dir)
def check_archive(archive_name): def check_archive(archive_name):
"""Check if existing archive files are (not) compressed as expected.""" """Check if existing archive files are (not) compressed as expected and
check if we can work with the destination directory."""
compressed_archive = archive_name + ".gz" compressed_archive = archive_name + ".gz"
if options.no_compress: if options.no_compress:
if os.path.isfile(compressed_archive): if os.path.isfile(compressed_archive):
@ -1693,6 +1695,10 @@ def check_archive(archive_name):
"Have you been reading this archive?\n" "Have you been reading this archive?\n"
"You probably should re-compress it manually, and try running me " "You probably should re-compress it manually, and try running me "
"again." % archive_name) "again." % archive_name)
dest_dir = os.path.dirname(archive_name)
if not dest_dir:
dest_dir = os.getcwd()
check_sane_destdir(dest_dir)
def nice_size_str(size): def nice_size_str(size):
"""Return given size in bytes as '12kB', '1.2MB'""" """Return given size in bytes as '12kB', '1.2MB'"""