#!/usr/bin/env python
#-*- coding: UTF-8 -*-

import tarfile
import zipfile 
import shutil
import re
import os
import os.path
import logging
import logging.handlers

from stomamisc import *
from stomasqlite import *
from time import strftime, sleep, localtime

UPD_INTERV = 32

class BackupExec(object):
    
    def __init__(self, getAbortAction, taskname, database, config, setStatusText = None, backupFiles = None):
  
        self.dbdirty = False
        self.getAbortAction = getAbortAction 
        self.task = config['Tasks'][taskname]
        self.database = database
        self.autoupdate = (database != None) and (config['Database']['autoupd'] == 'True')
        if setStatusText == None:
            self.setStatusText = self.dummySetStatusText()
        else:
            self.setStatusText = setStatusText
        self.priority = config['Misc']['priority'] == 'True'
        if backupFiles == None:
            self.backupFiles = self.dummyBackupFiles()
        else:
            self.backupFiles = backupFiles
        self.filecount = 0
        self.skipcount = 0
        self.errorcount = 0
        self.timestamp = strftime('%Y%m%d%H%M')
        
        self.sourcepath = self.task['sourcepath']
        self.targetpath = self.task['targetpath']
        self.withlog = config['Misc']['errorlog'] == 'True'
        if self.withlog:
            logfilename = TITLE + '-Backup.log'
            logpath = os.path.join(HOMEDIR, logfilename)
            self.logger = logging.getLogger(taskname)
            self.logger.setLevel(logging.INFO)
            self.handler = logging.handlers.RotatingFileHandler(logpath, 'a', BACKUP_LOG_SIZE, 4)
            formatter = logging.Formatter("%(asctime)s - %(name)s -- %(message)s", "%Y-%m-%d %H:%M:%S")
            self.handler.setFormatter(formatter)        
            self.logger.addHandler(self.handler)
            self.logger.info('%s started - %s' % (logfilename, VERSION))

        self.subinclude = self.task['subinclude'] == 'True'
        self.ts = self.task['timestamp'] == 'True'
        self.tsappend = self.task['tsappend'] == 'True'
        
        self.parsers = []
        for mask in self.task['exclude']:
            if len(mask):
                try:
                    p = re.compile(mask)
                    self.parsers.append(p)
                except:
                    pass
    #-------------------------------------------------------------------------------------------

    def dummySetStatusText(self, p1 = None, p2 = None):
        pass
    #-------------------------------------------------------------------------------------------

    def dummyBackupFiles(self, p1 = None, p2 = None, p3 = None):
        pass
    #-------------------------------------------------------------------------------------------
    
    def write2Logfile(self, logmsg):
        if self.withlog:
            self.logger.info(logmsg)
    #-------------------------------------------------------------------------------------------

    def checkMatch(self, pathname):
        if not LINUX:
            pathname = pathname.replace('\\', '/')
        match = False
        for parser in self.parsers:
            if parser.search(pathname): 
                match = True
                self.skipcount += 1
                break
        return match
    #-------------------------------------------------------------------------------------------

    def timeStamp(self, targetname, tail):
        if self.ts:
            if self.tsappend:
                targetname = os.path.join(targetname, tail)
                targetname = '%s-%s' % (targetname, self.timestamp)
            else:
                targetfile = '%s-%s' % (self.timestamp, tail)
                targetname = os.path.join(targetname, targetfile)
        else:
            targetname = os.path.join(targetname, tail)
        return targetname
    #-------------------------------------------------------------------------------------------

    def copyTree(self, src, dst):
        if self.checkMatch(src + os.sep): return
        names = os.listdir(src)
        if not os.path.exists(dst):
            os.mkdir(dst, 0777)
            dirnotok = True
            retry = 100
            testfile = os.path.join(dst, 'test')
            while dirnotok and retry:       # because of timing problems when using
                try:                        # smb. try 100 times.
                    f = file(testfile, 'w')
                    dirnotok = False
                except:
                    sleep(0.01)
                    retry -= 1
            try:
                os.remove(testfile)
            except:
                pass

        for name in names:
            if self.getAbortAction(): break
            srcname = os.path.join(src, name)
            dstname = os.path.join(dst, name)
            try:
                if os.path.isdir(srcname):
                    self.copyTree(srcname, dstname)
                else:
                    self.filecount += 1
                    if os.path.islink(srcname):
                        self.skipcount += 1
                        self.write2Logfile('Skipped (is link): %s' % (srcname))
                        continue                                            
                    if self.checkMatch(srcname):
                        self.write2Logfile('Skipped (exclude match): %s' % (srcname))
                        continue
                    self.dbdirty = os.path.exists(dstname)
                    shutil.copy2(srcname, dstname)
                    if self.autoupdate:
                        record = (self.targetpath, dstname[len(self.targetpath) +1:], os.path.getsize(dstname), strftime('%Y-%m-%d %H:%M', localtime(getmtime(dstname))))
                        self.database.putRecord(record)                        
                    if self.filecount % UPD_INTERV == 0: self._updateBackupFiles()
                        #except (IOError), why:
                        #    print "IOError: %s" % (str(why))
                        #    self.errorcount += 1
                        #except (os.error), why:
                        #    print "oserror: %s" % (str(why))
                        #    self.errorcount += 1
                        #except:
                        #    print 'panic: what is this'
            except (IOError):
                pass                
            except (os.error):
                self.errorcount += 1
                if self.withlog:
                    self.logger.info('Error: %s\n' % (srcname))
    #-------------------------------------------------------------------------------------------
    
    def noCompression(self):        
        if self.subinclude:
            self.copyTree(self.sourcepath, self.targetpath)
        else:
            dirlist = os.listdir(self.sourcepath)
            self.setStatusText(self.sourcepath, 3)
            for name in dirlist:
                if self.getAbortAction(): break
                spname = os.path.join(self.sourcepath, name)
                tpname = os.path.join(self.targetpath, name)
                try:
                    if os.path.isfile(spname):
                        self.filecount += 1
                        if os.path.islink(spname):
                            self.skipcount += 1
                            self.write2Logfile('Skipped (is link): %s' % (spname))
                            continue                                                
                        if self.checkMatch(spname):
                            self.write2Logfile('Skipped (exclude match): %s' % (spname))
                            continue
                        self.dbdirty = os.path.exists(tpname)
                        shutil.copy(spname, tpname)
                        if self.autoupdate:
                            record = (self.targetpath, tpname[len(self.targetpath) +1:], os.path.getsize(tpname), strftime('%Y-%m-%d %H:%M', localtime(getmtime(tpname))))
                            self.database.putRecord(record)                        
                        if self.filecount % UPD_INTERV == 0: self._updateBackupFiles()
                except:
                    self.errorcount += 1
                    self.write2Logfile('Error: %s' % (spname))
    #-------------------------------------------------------------------------------------------
    
    def compressTargz(self, mode):
        head, tail = os.path.split(self.sourcepath)
        if len(tail) and tail[0] == '.': tail = tail[1:]
        headlen = len(head)
        targetname = self.timeStamp(self.targetpath, tail)
        
        targetname = '%s.%s' % (targetname, mode)
        targetname = targetname.encode('utf-8')
        self.dbdirty = os.path.exists(targetname)
        
        if mode == 'tar':
            tf = tarfile.open(targetname, 'w')
        elif mode == 'tar.gz':
            tf = tarfile.open(targetname, 'w:gz')
        else:                                           # mode == 'tar.bz2'
            tf = tarfile.open(targetname, 'w:bz2')
                   
        if self.subinclude:
            for root, dirs, files in os.walk(self.sourcepath, onerror = None):
                if self.getAbortAction(): break
                self.setStatusText(root, 3)
                for name in files:
                    if self.getAbortAction(): break
                    self.filecount += 1
                    pname = os.path.join(root, name)
                    if os.path.islink(pname):
                        self.skipcount += 1
                        self.write2Logfile('Skipped (is link): %s' % (pname))
                        continue                                            
                    if self.checkMatch(pname):
                        self.write2Logfile('Skipped (exclude match): %s' % (pname))
                        continue               
                    try:
                        tf.add(pname, pname[headlen:])
                        if self.filecount % UPD_INTERV: self._updateBackupFiles()
                    except:
                        self.errorcount += 1
                        self.write2Logfile('Error: %s' % (pname))
        
        else:
            dirlist = os.listdir(self.sourcepath)
            self.setStatusText(self.sourcepath, 3)
            for name in dirlist:
                if self.getAbortAction(): break
                self.filecount += 1
                pname = os.path.join(self.sourcepath, name)
                if os.path.islink(pname):
                    self.skipcount += 1
                    self.write2Logfile('Skipped (is link): %s' % (pname))
                    continue                                        
                if self.checkMatch(pname):
                    self.write2Logfile('Skipped (exclude match): %s' % (pname))
                    continue               
                try:
                    if os.path.isfile(pname) and not os.path.islink(pname):
                        tf.add(pname,  pname[headlen:])
                        if self.filecount % UPD_INTERV == 0: self._updateBackupFiles()                      
                except:
                    self.errorcount += 1
                    self.write2Logfile('Error: %s' % (pname))                    

        tf.close()
        if self.autoupdate:
            record = (self.targetpath, targetname[len(self.targetpath) +1:], os.path.getsize(targetname), strftime('%Y-%m-%d %H:%M', localtime(getmtime(targetname))))
            self.database.putRecord(record)
    #-------------------------------------------------------------------------------------------

    def compressZip(self):
        head, tail = os.path.split(self.sourcepath)
        os.chdir(self.sourcepath)
        headlen = len(head)
        if len(tail) and tail[0] == '.': tail = tail[1:]
        targetname = self.timeStamp(self.targetpath, tail)
        targetname += '.zip'
        self.dbdirty = os.path.exists(targetname)
        zf = zipfile.ZipFile(targetname, 'w')
            
        if self.subinclude:
            for root, dirs, files in os.walk(self.sourcepath, onerror = None):
                if self.getAbortAction(): break
                self.setStatusText(root, 3)
                for name in files:
                    if self.getAbortAction(): break
                    self.filecount += 1
                    pname = os.path.join(root, name)
                    if os.path.islink(pname):
                        self.skipcount += 1
                        self.write2Logfile('Skipped (is link): %s' % (pname))
                        continue                        
                    if self.checkMatch(pname):
                        self.write2Logfile('Skipped (exclude match): %s' % (pname))
                        continue               
                    if os.path.getsize(pname) == 0:
                        self.skipcount += 1
                        self.write2Logfile('Skipped (size == 0): %s' % (pname))
                        continue
                    try:
                        zf.write(pname, pname[headlen:], zipfile.ZIP_DEFLATED)
                        if self.filecount % UPD_INTERV == 0: self._updateBackupFiles()                      
                    except:
                        self.errorcount += 1
                        self.write2Logfile('Error: %s' % (pname))
        else:
            dirlist = os.listdir(self.sourcepath)
            self.setStatusText(self.sourcepath, 3)
            for name in dirlist:
                if self.getAbortAction(): break
                self.filecount += 1
                pname = os.path.join(self.sourcepath, name)
                if os.path.islink(pname):
                    self.skipcount += 1
                    self.write2Logfile('Skipped (is link): %s' % (pname))
                    continue                                        
                if self.checkMatch(pname):
                    self.write2Logfile('Skipped (exclude match): %s' % (pname))
                    continue               
                if os.path.isfile(pname) and not os.path.islink(pname):
                    if os.path.getsize(pname) == 0:
                        self.skipcount += 1
                        self.write2Logfile('Skipped (size == 0): %s' % (pname))
                        continue
                    try:
                        zf.write(pname, pname[headlen:], zipfile.ZIP_DEFLATED)
                        if self.filecount % UPD_INTERV == 0: self._updateBackupFiles()
                    except:
                        self.errorcount += 1
                        self.write2Logfile('Error: %s' % (pname))                        
        zf.close()
        if self.autoupdate:
            record = (self.targetpath, targetname[len(self.targetpath) +1:], os.path.getsize(targetname), strftime('%Y-%m-%d %H:%M', localtime(getmtime(targetname))))
            self.database.putRecord(record)
    #-------------------------------------------------------------------------------------------

    def executeTask(self):
        if self.task['comprmeth'] in ['tar', 'tar.gz', 'tar.bz2']:
            self.compressTargz(self.task['comprmeth'])
        elif self.task['comprmeth'] == 'zip':
            self.compressZip()
        else:
            self.noCompression()
            
        self._updateBackupFiles()
        if self.withlog:
            self.logger.info('Summary: %d files, %d skipped, %d errors\n' % (self.filecount, self.skipcount, self.errorcount))
            self.logger.removeHandler(self.handler)
        return self.filecount, self.skipcount, self.errorcount, self.dbdirty
    #-------------------------------------------------------------------------------------------
         
    def _updateBackupFiles(self):
        self.backupFiles(self.filecount, self.skipcount, self.errorcount)
        if self.priority: sleep(0.01)
# - class BackupExec(object):---------------------------------------------------------------
    
#    
#if __name__ == '__main__':
#    task = {'sourcepath': '/data/tmpdata/snrechne',
#            'subinclude':  'True',
#            'targetpath': '/data/tmpdata/nastarget',
#            'override':    'False',
#            'comprmeth':   'zip',
#            'timestamp':   'True',
#            'tsappend':    'True',
#            'exclude':     ['(.*)~'],
#            'lastrun':     '2008-01-01 00:00',
#           }
#           
#    be = BackupExec(task)
#    fc, sc, ec = be.executeTask()
#    print fc, sc, ec
#    
#    