#
# ghettovcb.py
#
# mkbackup ghettovcb frontent
#

import os, string, subprocess, fnmatch
import posixpath

from datetime import datetime, timedelta

import paramiko
import scp


from archiver import *
import cron


# ---------------------------------------------------------------------------
class MySCPClient(scp.SCPClient):
    
    # -----------------------------------------------------------------------
    def _send_files(self, files): 
        for name in files:
            basename = os.path.basename(name)
            (mode, size, mtime, atime) = self._read_stats(name)
            if self.preserve_times:
                self._send_time(mtime, atime)
            file_hdl = file(name, 'rb')
            buffer=file_hdl.read()
            file_hdl.close()
            buffer=buffer.replace('\r', '')
            size=len(buffer)
            self.channel.sendall('C%s %d %s\n' % (mode, size, basename))
            self._recv_confirm()
            file_pos = 0
            buff_size = self.buff_size
            chan = self.channel
            while file_pos < size:
                buf=buffer[file_pos:file_pos+buff_size]
                chan.sendall(buf)
                file_pos += len(buf) 
                if self.callback:
                    self.callback(file_pos, size)
            chan.sendall('\x00')

# ---------------------------------------------------------------------------
def RunCommand(t, command, log=None):
    chan=t.open_session()
    exit_code=None
    chan.set_combine_stderr(True)
    chan.exec_command(command)
    output=''
    while 1:
        try:
            x=chan.recv(1024)
            if len(x)==0:
                break
            output+=x
        except socket.timeout:
            status=-1
            break

    if exit_code==None:
        exit_code=chan.recv_exit_status()
        chan.close()
        
    if log:
        l=log.debug
        if exit_code!=0:
            l=log.warning
            
        if output or exit_code!=0:
            l('code=%d command=%s', exit_code, command)
            for line in output.split('\n'):
                l('> %s', line)
        
    return exit_code, output
  

class Ghettovcb(Archiver):
    
    name='ghettovcb'
    exe='ghettovcb'

    types=dict(normal='normal', full='normal', incremental='incremental', inc='incremental', differential='differential', diff='differential', copy='copy', daily='daily')

    # -----------------------------------------------------------------------
    def __init__(self):
        Archiver.__init__(self)
        
        self.host=None
        self.login=None
        self.password=None
        self.local=None
        self.temp=None
        self.target=None
        self.local=None
        self.temp=None
        self.vm_list=None
        
        self.ghettovcb=None
        self.ghettovcb_bin=None
        self.temp_target=None
        self.current_vm=None
        self.vm_var=None
        self.default_var=None
        
        
    # -----------------------------------------------------------------------
    def read_vm_conf(self, filename, default={}):
        values=default.copy()
        for line in open(filename, 'r'):
            for var in ('VM_BACKUP_VOLUME', 'ENABLE_COMPRESSION'):
                if line.startswith('%s=' % var):
                    _k, v=line.split('=', 1)
                    values[var]=v.strip()
        print 'read_vm_conf', filename, values
        return values
    
    # -----------------------------------------------------------------------
    def load(self, job, log):
        errors, warnings, extra={}, {}, ''

        #
        # check job config
        #
#        host=192.168.1.11
#        login=root
#        password=secret
#        local=C:\Magik\vmware
#        temp=/vmfs/volumes/SATA500/tmp
#        target=/vmfs/volumes/BACKUP


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

        try:
            self.port=int(job.get('port', 22))
        except Exception:
            errors['port']='must be an integer'
        else:
            if not (0<self.port and self.port<65535):
                errors['port']='must be an integer between 1 and 65535'

        for name in ('login', 'password', 'local', 'remote_temp', 'vm_list',):
            value=job.get(name, None)
            if not value:
                errors[name]='option mandatory'
            else:
                setattr(self, name, value)

        try:
            self.vm_list=quoted_string_list(self.vm_list)
        except ValueError:
            errors['vm_list']='not a valid quoted string list'

        if job.get('target', None):
            self.target=job['target']

           
        if not os.path.isdir(self.local):
            errors['local']='not a directory'
        else:
            # search ghettoVCB.sh whatever the case
            self.ghettovcb=None
            for filename in os.listdir(self.local):
                if filename.lower()=='ghettovcb.sh':
                    self.ghettovcb=filename
                    break

            if not self.ghettovcb:
                errors['local']='ghettoVCB.sh not found'
                
            # read ghettoVCB.sh  and extract default variables
            self.default_var=self.read_vm_conf(os.path.join(self.local, self.ghettovcb))
            
            # read vm config file if it exist
            self.vm_var=dict()
            for current_vm in self.vm_list:
                filename=os.path.join(self.local, current_vm)
                if os.path.isfile(filename):
                    self.vm_var[current_vm]=self.default_var
                else:
                    self.vm_var[current_vm]=self.read_vm_conf(filename, self.default_var)
            

        if not errors:
            # upload and setup files on the VMWARE server

            t=paramiko.Transport((self.host, self.port))
            t.connect(username=self.login, password=self.password)
            
            scpclient=MySCPClient(t)
            scpclient.put(self.local, self.remote_temp, recursive=True)

            self.temp_target=posixpath.join(self.remote_temp, posixpath.basename(self.local))
            self.ghettovcb_bin=posixpath.join(self.temp_target, self.ghettovcb)
            self.current_vm_file=posixpath.join(self.temp_target, 'current_vm')
            
            _exit_code, _output=RunCommand(t, 'chmod +x "%s"' % self.ghettovcb_bin, log)

            self.cmd_line='cd "%s" ; ./"%s" -d debug -f "%s" -c "%s"' % (self.temp_target, self.ghettovcb, self.current_vm_file, self.temp_target)

            extra+='cmd_line=%s\n' % self.cmd_line
            t.close()
        
        return errors, warnings, extra


    # -----------------------------------------------------------------------
    def run(self, command, job, log):

        #print 'tar', command, job, log

        
        for self.current_vm in self.vm_list:
            
            self.sub_run(command, job, log)
            
        
    # -----------------------------------------------------------------------
    def sub_run(self, command, job, log):

        start=int(time.time())

        t=paramiko.Transport((self.host, self.port))
        t.connect(username=self.login, password=self.password)
        _exit_code, _output=RunCommand(t, 'echo "%s" > "%s"' % (self.current_vm, self.current_vm_file), log)
        chan=t.open_session()
        chan.set_combine_stderr(True)
        chan.exec_command(self.cmd_line)
        output, line_buf='', ''
        while True:
            x=chan.recv(1024)
            if len(x)==0:
                break
            output+=x
            line_buf=line_buf+x
            lines=line_buf.split('\n')
            line_buf=lines[-1]
            for line in lines[:-1]:
                log.debug(line)

        if line_buf:
            log.debug(line_buf)
            
        ghettovcb_exit_code=chan.recv_exit_status()
        chan.close()
        end=int(time.time())

        # make the target directory listing 
        target=self.vm_var[self.current_vm]['VM_BACKUP_VOLUME']
        compress=self.vm_var[self.current_vm]['ENABLE_COMPRESSION']=='1'
        dir_lst=None
        if compress:
            _exit_code, dir_lst=RunCommand(t, 'ls -l "%s"' % posixpath.join(target, self.current_vm), log)
        else:
            _exit_code, up_lst=RunCommand(t, 'ls "%s"' % posixpath.join(target, self.current_vm), log)
            dir_lst=None
            for line in up_lst.split('\n'):
                if line.startswith(self.current_vm) and line.endswith('--1'):
                     _exit_code, dir_lst=RunCommand(t, 'ls -l "%s"' % posixpath.join(target, self.current_vm, line), log)
                     break

        # make the disk space usage
        _exit_code, df=RunCommand(t, 'df', log)

        t.close()

        # print 'returncode find=%r, tar=%r' % (p_find.returncode, p_tar.returncode)
        if ghettovcb_exit_code==0:
            backup_status='OK'
        else:
            backup_status='ERR'

        status=u''
        status+='status=%s\n' % backup_status        
        status+='exit_code=%d\n' % ghettovcb_exit_code
        status+='start=%s\r\n' % time.ctime(start)
        status+='end=%s\r\n' % time.ctime(end)
        
        status+='start_epoch=%d\r\n' % start
        status+='end_epoch=%d\r\n' % end

        for line in status.split('\n'):
            if line:
                log.info('    %s', line)
        
        #
        # dir of target directory
        #

        attachements=[ # the type, subtype and coding is not used
                       ('config.ini', job['config'], None, 'text', 'plain', job['config_encoding']),
                       ('status.txt', None, status.encode('utf-8'), 'text', 'plain', 'utf-8'),
                       ('output.txt', None, output, 'text', 'plain', 'utf-8'),
                       ('df.txt', None, df, 'text', 'plain', 'utf-8'),
                    ]
        if dir_lst:
            attachements.append(('dir.txt', None, dir_lst, 'text', 'plain', 'utf-8'))
            
#        if std_out:
#            attachements.append(('stdout.txt', None, std_out, 'text', 'plain', 'cp850'))
#        if std_err:
#            attachements.append(('stderr.txt', None, std_err, 'text', 'plain', 'cp850'))
        
        msg_body=job['msg_header']+'\n'+status
        subject='MKBACKUP BACKUP %s %s.%s' % (backup_status, job['name'], self.current_vm)
        sendmail(job['sender'], job['recipients'], subject, msg_body, attachements, job['smtp_host'], job['smtp_port'])

