#
# archiver.py
#

import os, sys, socket, re, time, smtplib


import email
from email.Utils import parseaddr, formataddr, formatdate
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase



import cron

atom=r"[a-zA-Z0-9_#\$&'*+/=?\^`{}~|\-]+"
dot_atom=atom  +  r"(?:\."  +  atom  +  ")*"
quoted=r'"(?:\\[^\r\n]|[^\\"])*"'
local="(?:"  +  dot_atom  +  "|"  +  quoted  +  ")"
domain_lit=r"\[(?:\\\S|[\x21-\x5a\x5e-\x7e])*\]"
domain="(?:"  +  dot_atom  +  "|"  +  domain_lit  +  ")"
addr_spec=local  +  "\@"  +  domain
postfix_restricted_rfc2822_address_name=local
postfix_restricted_rfc2822_email_address=addr_spec
cyrus_mailbox_name=r"[a-zA-Z0-9_#$'=`{}~|-]+(?:\.[a-zA-Z0-9_#$'=`{}~|-]+)*"

domain_nameRE=re.compile('^'+dot_atom+'$')
email_addressRE=re.compile('^'+postfix_restricted_rfc2822_email_address+'$')
valid_ipRE=re.compile('^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$')
valid_hostnameRE=re.compile('^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$')


boolean={ 'on':True, 'yes':True, 'true':True, '1':True, 1:True, 'off':False, 'no':False, 'false':False, '0':False, 0:False}

# ---------------------------------------------------------------------
def check_boolean(job, errors, options):
    for option, default in options:
        value=boolean.get(job.get(option, default).lower(), None)
        if value==None:
            errors[option]='boolean must have value in (on, yes, true, 1, off, no, false and 0)'
            value=default
        job[option]=value


# ---------------------------------------------------------------------------
def check_mail_config(job):
    
    errors={}

    try:
        job['smtp_host']=smtp_host=job['smtp_host'].encode('ascii')
    except UnicodeEncodeError:
        errors['smtp_host']='invalid hostname or ip address'
    else:
        if not valid_ipRE.match(smtp_host):
            if not valid_hostnameRE.match(smtp_host):
                errors['smtp_host']='invalid hostname or ip address'
            else:
                try:
                    ip=socket.gethostbyname(smtp_host)
                except socket.gaierror:
                    errors['smtp_host']='cannot resolve address'

    smtp_port=job['smtp_port']
    try:
        port=int(smtp_port)
    except Exception:
        errors['smtp_port']='must be an integer'
    else:
        if not (0<port and port<65535):
            errors['smtp_port']='must be an integer between 1 and 65535'
        else:
            job['smtp_port']=port
        
    sender=job.get('sender', None)
    if not sender:
        errors['sender']='option mandatory'
    elif not email_addressRE.match(sender):
        errors['sender']='invalid email address'

    recipients=job.get('recipients')
    if not recipients:
        errors['recipients']='option mandatory'
    else:
        recipients=recipients.split()
        bad=[]
        for recipient in recipients:
            if not email_addressRE.match(recipient):
                bad.append(recipient)
        if bad:
            errors['recipients']='invalid email addresses: %s' % ' '.join(bad)
        else:
            job['recipients']=recipients

    return errors


# ---------------------------------------------------------------------------
def list_dir(target_dir, log):
    try: 
        dir_out=u'  epoch   |          time          |      size     |    filename\r\n'
        for filename in os.listdir(target_dir): # if target_dir is unicode, return os.listdir return unicode
            full_filename=os.path.join(target_dir, filename)
            stat=os.stat(full_filename)
            if os.path.isdir(full_filename):
                size='<DIR>'
            else:
                size='%15d' % stat.st_size 
            dir_out+='%d %s %15s %s\r\n' % (stat.st_mtime, time.ctime(stat.st_mtime), size, filename)
    except Exception, e:
        log.error('listing target directory: %s', e)
        dir_out='error listing directory "%s": %s\r\n' % (target_dir, e)
        
    return dir_out

# ---------------------------------------------------------------------------
def free_space(target_dir, log):
    
    try:
        if sys.platform=='win32':
            import win32file
            free_bytes, total_bytes, total_free_bytes=win32file.GetDiskFreeSpaceEx(target_dir)
        else:
            import os, statvfs
            s=os.statvfs(target_dir)
            total_free_bytes=s[statvfs.F_BSIZE]*s[statvfs.F_BAVAIL] 

    except Exception, e:
        log.error('checking directory "%s": %s', target_dir, e)
        return e

    return total_free_bytes

# ---------------------------------------------------------------------------
def quoted_string_list(st):
    """
    st  is a list of string separated by spaces like in shell script
    String containing space can be quoted
    Quote can be escaped with \
    r'1 two "t h r e e" "with a \" inside"' -->
                        ['1', 'two', 't h r e e', 'with a \\" inside']
    '" dont remove this line my editor string highlighting need it
    """                        
    lst=[]
    s=st.strip()
    while s:
        match=re.match(r'("(?:\\"|[^"])*"|[^ "]*)(.*)',  s)
        if match:
            item=match.groups()[0]
            if not item:
                raise ValueError, ("not a quoted string list '%s'" % (st,))
            elif item[0]=='"':
                lst.append(item[1:-1])
            else:
                lst.append(item)
            s=match.groups()[1].lstrip()
        else:
            raise ValueError, ("not a quoted string list '%s'" % (st,))
    return lst

# ---------------------------------------------------------------------------
def sendmail(sender, recipients, subject, text, attachements, smtp_host, smtp_port):

    msg=MIMEMultipart()
    msg.preamble='' # This line is not visible on mime enable MUA
    msg.epilogue=''
    
    msg['From'] = formataddr((sender, sender)) # Display name, email address
    msg['To'] =  ', '.join([ formataddr((recipient, recipient)) for recipient in recipients ])
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    core=MIMEText(text.encode('utf-8'), 'plain', 'utf-8')
    msg.attach(core)
    
    for filename, target, data, maintype, subtype, charset in attachements:
        
        if not data and target and os.path.exists(target):
            data=open(target, 'rb').read()

        if data:
            #maintype, subtype = 'application', 'octet-stream'
            if maintype=='text':
                attachement=MIMEText(data, subtype, charset)
            else:
                attachement=MIMEBase(maintype, subtype)
                attachement.set_payload(data) #, charset) #dont use the charset, here !
                email.Encoders.encode_base64(attachement)
    
        attachement.add_header('Content-Disposition', 'attachment', filename=filename)
        msg.attach(attachement)
    
    body=msg.as_string()
    
    smtp=smtplib.SMTP(smtp_host, smtp_port)
    ret=smtp.sendmail(sender, recipients, body)

#======================================================================
#
# Destinations
#
#======================================================================
class DestinationSyntaxError(Exception):
    pass

class Destinations:
    def __init__(self, raw, archiver):
        
        self.destinations=[]
        st=raw
        self.max_weekdivisor=1
        
        while st:
            if st[0]=='<':
                try:
                    pos=st.index('>')
                except ValueError:
                    raise DestinationSyntaxError, 'a ">" is missing'
                
                selector=st[1:pos]
                st=st[pos+1:]
            
                pos=st.find('<')
                if pos>0:
                    target=st[:pos]
                    st=st[pos:]
                else:
                    target=st
                    st=''
                
                typ, period=selector.split('=', 1)
                
                try:
                    if typ.lower()=='none':
                        typ='none'
                    else:
                        typ=archiver.types[typ.lower()]
                except KeyError:
                    raise DestinationSyntaxError, 'type "%s" unknow' % (typ, )

                #print "SELECTOR<%s=%s>%s" % (typ,period,target )
                #print "CARRY=%s" % st
                period=cron.Cron(period)
                if period.weekdivisor!=None:
                    self.max_weekdivisor=max(self.max_weekdivisor, period.weekdivisor)
            else:
                typ='full'
                period=None
                target=st
                st=''
                
            self.destinations.append((typ, period, target))


    def match(self, day):
        #print '---------------------------------'
        globals=dict(day=day.day, month=day.month, year=day.year, weekday=day.weekday, yearday=day.timetuple().tm_yday)
        for typ, period, target in self.destinations:
            #print typ, period, target
            if period:
                #print 'Month', day.month-1, period.months
                if period.months and not day.month-1 in period.months:
                    continue
                #print 'Day of week', day.isoweekday()-1, period.daysofweek
                if period.daysofweek and not day.isoweekday()-1 in period.daysofweek:
                    continue
                #print 'Day of month', day.day-1, period.daysofmonth
                if period.daysofmonth and not day.day-1 in period.daysofmonth:
                    continue
                if period.firstdayofweek!=None:
                    epoch=calendar.timegm(day.timetuple())
                    nday=int(epoch/86400)-period.firstdayofweek+3
                    week=int(nday/7)%period.weekdivisor
                    if period.weekselector!=None and week!=period.weekselector:
                        continue
                    globals['epoch']=epoch
                    globals['week']=week

            #print 'MATCH', typ, target
            
            for exp in re.findall('\{([^}]*)\}',target):
                target=target.replace('${%s}' % (exp,), str(eval(exp, globals, {})))
            target=target.encode('utf-8')
            target=day.strftime(target)
            target=target.decode('utf-8')

            return typ, target
            
        return None, None

# ---------------------------------------------------------------------------
class Archiver:
    
    
    name=None
    exe=None
 
    def __init__(self):
        pass


    
    # -----------------------------------------------------------------------
