mirror of
https://git.code.sf.net/p/archivemail/code
synced 2024-12-22 07:42:55 +00:00
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:
parent
910b507a2d
commit
f08403c99b
1 changed files with 129 additions and 123 deletions
102
archivemail
102
archivemail
|
@ -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'"""
|
||||
|
|
Loading…
Reference in a new issue