#!/usr/bin/env python
#
#    imap2mbox.py v1.2.0
#
#    Copyright (C) 2008  Michele <o-zone@zerozone.it> Pinassi
#
#    This software is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This software 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
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    1/9/2008 - First public release
#    29/10/2008 (v1.1.0) Added patches from Gregory Boyce <gboyce@badbelly.com> 
#       (OptionParser, SSL, read-only, skip not-matched) and some small 
#       fixes by me
#    18/11/2008 (v1.2.0) New patches from Gregory Boyce <gboyce@badbelly.com> 
#        that adds support for Exchange. Added compatibility with Python 2.3

""" Export your remote IMAP4 folders to local "mbox" file
    imap2mbox simply connects to remote IMAP4 server and recursively saves all 
    directories (and related messages) to mbox files. After, you can use 
    MHonarc to create an HTML archive ready to be burned into CDs ! 
"""

import getpass, imaplib, os, email, sys, re
from optparse import OptionParser

if sys.version_info[0] >= 2 and sys.version_info[1] >= 5:
    # do stuff for Python 2.5
    from email.utils import unquote
else:
    from rfc822 import unquote

from fnmatch import fnmatch

# Parse list
def parse_list():
    """ parses a list enty """
    listparser = re.compile('(\(.*\)) (".*") (.*)')
    return listparser.match(mbox).groups() 


class ProgressBar:
    """ a simple progress bar """
    def __init__(self, min_value = 0, max_value = 10, total_width=12):
        """ constructor for progress bar """
        self.prog_bar = "[]"   # This holds the progress bar string
        self.min = min_value
        self.max = max_value
        self.span = max_value - min_value
        self.width = total_width
        self.amount = 0       # When amount == max, we are 100% done 
        self.update_amount(0)  # Build progress bar string

    def update_amount(self, new_amount = 0):
        """ updates the value of progress bar """
        if new_amount < self.min: 
            new_amount = self.min
        if new_amount > self.max: 
            new_amount = self.max
        self.amount = new_amount

        # Figure out the new percent done, round to an integer
        diff_from_min = float(self.amount - self.min)
        percent_done = (diff_from_min / float(self.span)) * 100.0
        percent_done = round(percent_done)
        percent_done = int(percent_done)

        # Figure out how many hash bars the percentage should be
        all_full = self.width - 2
        num_hashes = (percent_done / 100.0) * all_full
        num_hashes = int(round(num_hashes))

        # build a progress bar with hashes and spaces
        self.prog_bar = "[" + '#'*num_hashes + ' '*(all_full-num_hashes) + "]"

        # figure out where to put the percentage, roughly centered
        percent_place = (len(self.prog_bar) / 2) - len(str(percent_done)) 
        percent_string = str(percent_done) + "%"

        # slice the percentage into the bar
        self.prog_bar = self.prog_bar[0:percent_place] + percent_string + \
                self.prog_bar[percent_place+len(percent_string):]

    def __str__(self):
        return str(self.prog_bar)



# Parse commandline OPTIONS
PARSER = OptionParser()
PARSER.add_option("-p", "--port", type="int", help="Port to connect to")
PARSER.add_option("-u", "--username", default=getpass.getuser(), 
        help="Username (defaults to current user)")
PARSER.add_option("-w", "--password", default=getpass.getpass(), 
        help="Specify Password (less secure)")
PARSER.add_option("-f", "--folder", default="*", 
        help="Select folders to download (wildcards supported)")
PARSER.add_option("-s", "--ssl", action="store_true", 
        help="Enable SSL")                  
PARSER.add_option("-r", "--readonly", action="store_true", 
        help="Do not mark messages as read")                  
PARSER.add_option("-t", "--test", action="store_true", help="Test Mode")

(OPTIONS, ARGS) = PARSER.parse_args()



# Grab the servername from the commandline OPTIONS
if len(ARGS) != 1:
    PARSER.error("Argument 'server' missing.")
IMAPHOST = ARGS[0]

IMAPUSER = OPTIONS.username

# Vai !

try:
    # If SSL...
    if OPTIONS.ssl:
        if OPTIONS.port:
            IMAP4 = imaplib.IMAP4_SSL(IMAPHOST, OPTIONS.port)
        else:
            IMAP4 = imaplib.IMAP4_SSL(IMAPHOST)
    # else normal...
    else:
        if OPTIONS.port:
            IMAP4 = imaplib.IMAP4(IMAPHOST, OPTIONS.port)
        else:
            IMAP4 = imaplib.IMAP4(IMAPHOST)
    # Let's go !
    IMAP4.login(IMAPUSER, OPTIONS.password)
    if OPTIONS.readonly:
        IMAP4.select(readonly=True)
    else:
        IMAP4.select()
except Exception, e:
    print "Error: %r\n"% (e)
    sys.exit()

if OPTIONS.test:
    RET, MBOXES = IMAP4.list()
    for mbox in MBOXES:
        hasChild, dot, mbName = parse_list(mbox)
        mbName = unquote(mbName)
        try:
            if OPTIONS.readonly:
                RET, numMex = IMAP4.select(mbName, readonly=True)
            else:
                RET, numMex = IMAP4.select(mbName)
            print "%s has %d messages"% (mbName, int(numMex[0]))
        except Exception, e:
            print "Error: %r\n"% e
            
    sys.exit()

try:
    os.mkdir(IMAPHOST)
except OSError:
    print "error mkdir %s"% (IMAPHOST)
os.chdir(IMAPHOST)
try:
    os.mkdir(IMAPUSER)
except OSError:
    print "error mkdir %s"% (IMAPHOST)
os.chdir(IMAPUSER)
# Inizia ad importare la casella postale
RET, MBOXES = IMAP4.list()
for mbox in MBOXES:
    hasChild, dot, mbName = parse_list(mbox)
    mbName = unquote(mbName)
    if OPTIONS.readonly:
        RET, numMex = IMAP4.select(mbName, readonly=True)
    else:
        RET, numMex = IMAP4.select(mbName)
    if fnmatch(mbName, OPTIONS.folder):
        if int(numMex[0]) > 0:
            mbName = re.sub("/", ".", mbName)
            if os.path.isfile("%s.mbox"% (mbName)):
                print "%s.mbox exists. Skipping !"% (mbName)
            else:
                mboxFd = os.open("%s.mbox"% (mbName), os.O_WRONLY | os.O_CREAT)
                numMex = int(numMex[0])
                print "Running on %s (%d messages):"% (mbName, numMex)
                RET, data = IMAP4.search(None, 'ALL')
                pBar = ProgressBar(0, numMex, 20)
                for num in data[0].split():
                    # Preleva messaggio
                    RET, data = IMAP4.fetch(num, '(RFC822)')
                    msgData = email.message_from_string(data[0][1]) 
                    # Scrivi MESSAGGIO
                    os.write(mboxFd, str(msgData))
                    os.write(mboxFd, "\n")
                    print pBar, "\r",
                    pBar.update_amount(int(num))
                    sys.stdout.flush()
                # Finito !
                print pBar, "\r",
                pBar.update_amount(numMex)
                sys.stdout.flush()
                print "\n"
                # Chiudi file mbox
                os.close(mboxFd)
        else:
            print "Skip empty mbox %s"% (mbName)
        
# Chiudi tutto !
IMAP4.close()
IMAP4.logout()
        
