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()
os.umask(077) # saves setting permissions on mailboxes/tempfiles
final_archive_name = make_archive_name(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)
vprint("processing '%s'" % mailbox_name)
is_imap = urlparse.urlparse(mailbox_name)[0] in ('imap', 'imaps')
if not is_imap:
# Check if the mailbox exists, and refuse to mess with other people's
@ -1121,19 +1115,19 @@ def archive(mailbox_name):
if is_imap:
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):
vprint("guessing mailbox is of type: mbox")
_archive_mbox(mailbox_name, final_archive_name)
_archive_mbox(mailbox_name)
elif os.path.isdir(mailbox_name):
cur_path = os.path.join(mailbox_name, "cur")
new_path = os.path.join(mailbox_name, "new")
if os.path.isdir(cur_path) and os.path.isdir(new_path):
vprint("guessing mailbox is of type: maildir")
_archive_dir(mailbox_name, final_archive_name, "maildir")
_archive_dir(mailbox_name, "maildir")
else:
vprint("guessing mailbox is of type: MH")
_archive_dir(mailbox_name, final_archive_name, "mh")
_archive_dir(mailbox_name, "mh")
else:
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
clean_up()
def _archive_mbox(mailbox_name, final_archive_name):
"""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
"""
def _archive_mbox(mailbox_name):
"""Archive a 'mbox' style mailbox - used by archive_mailbox()"""
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)
cache = IdentityCache(mailbox_name)
original = Mbox(path=mailbox_name)
@ -1234,11 +1223,13 @@ def _archive_mbox(mailbox_name, final_archive_name):
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()"""
assert mailbox_name
assert final_archive_name
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)
delete_queue = []
@ -1286,10 +1277,9 @@ def _archive_dir(mailbox_name, final_archive_name, type):
if not options.quiet:
stats.display()
def _archive_imap(mailbox_name, final_archive_name):
def _archive_imap(mailbox_name):
"""Archive an imap mailbox - used by archive_mailbox()"""
assert mailbox_name
assert final_archive_name
import imaplib
import cStringIO
import getpass
@ -1297,10 +1287,8 @@ def _archive_imap(mailbox_name, final_archive_name):
vprint("Setting imaplib.Debug = %d" % options.debug_imap)
imaplib.Debug = options.debug_imap
archive = None
stats = Stats(mailbox_name, final_archive_name)
cache = IdentityCache(mailbox_name)
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)
if not imap_password:
if options.pwfile:
@ -1327,6 +1315,16 @@ def _archive_imap(mailbox_name, final_archive_name):
user_error("imap server %s has login disabled (hint: "
"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)
total_msg_count = int(imap_srv.response("EXISTS")[1][0])
vprint("folder has %d message(s)" % total_msg_count)
@ -1402,11 +1400,12 @@ def _archive_imap(mailbox_name, final_archive_name):
'+FLAGS.SILENT', '\\Deleted')
if result != 'OK': unexpected_error("Error while deleting "
"messages; server says '%s'" % response[0])
vprint("Closing mailbox and terminating connection.")
vprint("Closing mailbox.")
imap_srv.close()
imap_srv.logout()
if not options.quiet:
stats.display()
vprint("Terminating connection.")
imap_srv.logout()
############### IMAP functions ###############
@ -1497,9 +1496,7 @@ def imap_get_namespace(srv):
def imap_smart_select(srv, mailbox):
"""Select the given mailbox on the IMAP server, correcting an invalid
mailbox path if possible."""
mailbox = imap_find_mailbox(srv, mailbox)
"""Select the given mailbox on the IMAP server."""
roflag = options.dry_run or options.copy_old_mail
# Work around python bug #1277098 (still pending in python << 2.5)
if not roflag:
@ -1528,10 +1525,13 @@ def imap_smart_select(srv, mailbox):
"upon SELECT")
def imap_find_mailbox(srv, mailbox):
"""Find the given mailbox on the IMAP server, correcting an invalid
mailbox path if possible. Return the found mailbox name."""
def imap_find_mailboxes(srv, mailbox):
"""Find matching mailboxes on the IMAP server, correcting an invalid
mailbox path if possible."""
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)
result, response = srv.list(pattern=curbox)
if result != 'OK':
@ -1544,13 +1544,18 @@ def imap_find_mailbox(srv, mailbox):
break
else:
user_error("Cannot find mailbox '%s' on server." % mailbox)
vprint("Found mailbox '%s'" % curbox)
# Catch \NoSelect here to avoid misleading errors later.
m = re.match(r'\((?P<attrs>[^\)]*)\)', response[0])
if '\\noselect' in m.group('attrs').lower().split():
user_error("Server indicates that mailbox '%s' is not selectable" \
% curbox)
return curbox
mailboxes = []
for mailbox_data in response:
m = re.match(r'\((.*?)\) "." "(.*?)"', mailbox_data)
attrs, name = m.groups()
if '\\noselect' in attrs.lower().split():
vprint("skipping not selectable mailbox '%s'" % name)
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):
@ -1656,10 +1661,6 @@ def make_archive_name(mailbox_name):
prefix = time.strftime(options.archive_prefix, tm)
if options.archive_suffix:
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)
if not prefix:
# 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)
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"
if options.no_compress:
if os.path.isfile(compressed_archive):
@ -1693,6 +1695,10 @@ def check_archive(archive_name):
"Have you been reading this archive?\n"
"You probably should re-compress it manually, and try running me "
"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):
"""Return given size in bytes as '12kB', '1.2MB'"""