#!/usr/bin/python
#coding: utf-8
#-----------------------------
# 宝塔Windows面板网站备份工具
#-----------------------------

import sys,os,json
panelPath = os.getenv('BT_PANEL')
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public,db,time,panelMssql

class backupTools:

    def backupSite(self,name,count):

        path = public.M('sites').where('name=?',(name.strip(),)).getField('path');
        startTime = time.time()
        if not path:
            endDate = time.strftime('%Y/%m/%d %X',time.localtime())
            log = u"网站["+name+"]不存在!"
            print(u"["+endDate+"] "+log)
            print("----------------------------------------------------------------------------")
            return

        backup_path = public.M('config').where("id=?",(1,)).getField('backup_path') + '/site';

        if not os.path.exists(backup_path):  os.makedirs(backup_path)

        filename= backup_path + "/Web_" + name + "_" + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.zip'
        self.Zip(path,filename)

        endDate = time.strftime('%Y/%m/%d %X',time.localtime())

        if not os.path.exists(filename):
            log = u"网站["+name+u"]备份失败!"
            print(u"["+endDate+"] "+log)
            print(u"----------------------------------------------------------------------------")
            return

        outTime = time.time() - startTime
        pid = public.M('sites').where('name=?',(name,)).getField('id')
        public.M('backup').add('type,name,pid,filename,addtime,size',('0',os.path.basename(filename),pid,filename,endDate,os.path.getsize(filename)))
        log = u"网站["+name+u"]备份成功,用时["+str(round(outTime,2))+u"]秒"
        public.WriteLog(u'计划任务',log)
        print(u"["+endDate+"] " + log)
        print(u"|---保留最新的["+count+u"]份备份")
        print(u"|---文件名:"+filename)
        print(u"|---备份大小:"+public.to_size(os.path.getsize(filename)))

        #清理多余备份
        backups = public.M('backup').where('type=? and pid=? and filename not like \'%|%\'',('0',pid)).field('id,name,filename').select()

        # 跳过手动备份文件
        new_backups = []
        for i in range(len(backups)):
            if backups[i]['name'][:4].lower() == 'web_': # 网站备份
                new_backups.append(backups[i])

        backups = new_backups[:]

        num = len(backups) - int(count)
        if  num > 0:
            for backup in backups:
                if os.path.exists(backup['filename']): os.remove(backup['filename'])
                public.M('backup').where('id=?',(backup['id'],)).delete()
                num -= 1
                print(u"|---已清理过期备份文件：" + backup['filename'])
                if num < 1: break


    """
    @备份数据库
    """
    def backupDatabase(self,name,count):
        import re

        sql = db.Sql()
        find = public.M('databases').where('name=?',(name,)).find()
        startTime = time.time()
        endDate = time.strftime('%Y/%m/%d %X',time.localtime())
        if not find:
            log = u"数据库["+name+u"]不存在!"
            print(u"["+endDate+"] "+log)
            print(u"----------------------------------------------------------------------------")
            return

        backup_path = public.M('config').where("id=?",(1,)).getField('backup_path') + '/database'
        if not os.path.exists(backup_path): os.makedirs(backup_path)

        sqlfile = backup_path + "/Db_" + name + "_" + time.strftime('%Y%m%d_%H%M%S',time.localtime())+".sql"
        db_find = find
        #执行MySQL备份
        if find['type'].lower() == 'mysql':
            #备份本地数据库
            mysql_root = public.M('config').where("id=?",(1,)).getField('mysql_root')
            if public.get_server_status('mysql') < 0:
                log = u"未安装MySQL服务!"
                print(u"["+endDate+"] "+log)
                print(u"----------------------------------------------------------------------------")
                return

            #取mysql安装目录
            _version = re.search('([MySQL|MariaDB-]+\d+\.\d+)',public.get_server_path('mysql')).groups()[0]
            _setup_path  = public.GetConfigValue('setup_path') + '/mysql/' + _version

            conn_config = {}
            self._db_mysql = public.get_mysql_obj(name)
            is_cloud_db = db_find['db_type'] in ['1',1,'2',2]
            if is_cloud_db:

                # 连接远程数据库
                if db_find['sid']:
                    conn_config = public.M('database_servers').where('id=?',db_find['sid']).find()
                    if not 'db_name' in conn_config: conn_config['db_name'] = None
                else:
                    conn_config = json.loads(db_find['conn_config'])
                conn_config['db_port'] = str(int(conn_config['db_port']))
                if not self._db_mysql or  not self._db_mysql.set_host(conn_config['db_host'],int(conn_config['db_port']),conn_config['db_name'],conn_config['db_user'],conn_config['db_password']):
                    error_msg = "连接远程数据库[{}:{}]失败".format(conn_config['db_host'],conn_config['db_port'])
                    print(error_msg)
                    return False

                public.ExecShell(_setup_path + "/bin/mysqldump.exe -R -E --force --hex-blob --opt -h "+ conn_config['db_host'] +" --default-character-set="+ public.get_database_character(name) +" -P" + str(conn_config['db_port']) + " -u "+str(conn_config['db_user'])+" -p" + str(conn_config['db_password']) + " -R " + name + " > " + sqlfile)
            else:
                try:
                    myconf = public.readFile(_setup_path + '/my.ini')
                    rep = "port\s*=\s*([0-9]+)"
                    port = re.search(rep,myconf).groups()[0].strip()
                except :
                    port = '3306'

                public.ExecShell(_setup_path + "/bin/mysqldump.exe -R -E --force --hex-blob --opt --default-character-set="+ public.get_database_character(name) +" -P" + port + " -uroot -p" + mysql_root + " -R " + name + " > " + sqlfile)

            if not os.path.exists(sqlfile):
                endDate = time.strftime('%Y/%m/%d %X',time.localtime())
                log = u"数据库["+name+u"]备份失败!"
                print(u"["+endDate+"] "+log)
                print(u"----------------------------------------------------------------------------")
                return
        else:
            if find['type'].lower() != 'sqlserver':
                print(u"不支持的数据库类型：{} - 数据库名：{}".format(find['type'],name))
                return

            if public.get_server_status('MSSQLSERVER') < 0:
                log = u"未安装SQLServer服务!"
                print(u"["+endDate+"] "+log)
                print(u"----------------------------------------------------------------------------")
                return

            sqlfile = sqlfile.replace('.sql','.bak')
            mssql_obj = panelMssql.panelMssql()
            mssql_obj.execute("backup database %s To disk='%s'" % (name,sqlfile))

        filename = sqlfile.replace(".bak",".zip").replace(".sql",".zip")
        self.Zip(sqlfile,filename)
        try:
            public.rmdir(sqlfile)
        except:pass

        endDate = time.strftime('%Y/%m/%d %X',time.localtime())
        outTime = time.time() - startTime
        pid = public.M('databases').where('name=?',(name,)).getField('id')

        public.M('backup').add('type,name,pid,filename,addtime,size',(1,os.path.basename(filename),pid,filename,endDate,os.path.getsize(filename)))
        log = u"数据库["+name+u"]备份成功,用时["+str(round(outTime,2))+u"]秒"
        public.WriteLog(u'计划任务',log)
        print("["+endDate+"] " + log)
        print(u"|---保留最新的[" + count + u"]份备份")
        print(u"|---文件名:" + filename)
        print(u"|---备份大小:" + public.to_size(os.path.getsize(filename)))

        #清理多余备份
        backups = public.M('backup').where('type=? and pid=? and filename not like \'%|%\'',('1',pid)).field('id,name,filename').select()

        # 跳过手动备份文件
        new_backups = []
        for i in range(len(backups)):
            if backups[i]['name'][:3].lower() =='db_': # 数据库备份
                new_backups.append(backups[i])

        backups = new_backups[:]
        num = len(backups) - int(count)
        if  num > 0:
            for backup in backups:
                public.M('backup').where('id=?',(backup['id'],)).delete()
                if os.path.exists(backup['filename']): os.remove(backup['filename'])
                num -= 1
                print(u"|---已清理过期备份文件：" + backup['filename'])
                if num < 1: break

    #备份指定目录
    def backupPath(self,path,count):
        sql = db.Sql()
        startTime = time.time()
        name = os.path.basename(path)
        backup_path = public.M('config').where("id=?",(1,)).getField('backup_path') + '/path'
        if not os.path.exists(backup_path): os.makedirs(backup_path)
        filename= backup_path + "/Path_" + name + "_" + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.zip'

        self.Zip(path,filename)

        endDate = time.strftime('%Y/%m/%d %X',time.localtime())
        if not os.path.exists(filename):
            log = u"目录["+path+"]备份失败"
            print(u"["+endDate+"] "+log)
            print(u"----------------------------------------------------------------------------")
            return
        try:
            path_str = public.format_path(path.encode('gbk').decode('utf8'))
        except:
            path_str = public.format_path(path)
        outTime = time.time() - startTime
        public.M('backup').add('type,name,pid,filename,addtime,size',('2',path,'0',filename,endDate,os.path.getsize(filename)))
        log = u"目录[" + path_str + " ]备份成功,用时["+str(round(outTime,2))+"]秒"
        public.WriteLog(u'计划任务',log)
        print(u"["+endDate+"] " + log)
        print(u"|---保留最新的["+count+u"]份备份")
        print(u"|---文件名:"+filename)
        print(u"|---备份大小:"+public.to_size(os.path.getsize(filename)))
        #清理多余备份
        backups = public.M('backup').where('type=? and pid=? and name=? and filename not like \'%|%\'',('2',0,path)).field('id,name,filename').select()

        # 跳过手动备份文件
        new_backups = []
        for i in range(len(backups)):
            fname = os.path.basename(backups[i]['filename'])
            if fname[:5].lower() == 'path_': # 目录备份
                new_backups.append(backups[i])
        backups = new_backups[:]

        num = len(backups) - int(count)
        if  num > 0:
            for backup in backups:
                if os.path.exists(backup['filename']): os.remove(backup['filename'])
                public.M('backup').where('id=?',(backup['id'],)).delete();
                num -= 1
                print(u"|---已清理过期备份文件：" + backup['filename'])
                if num < 1: break


    def backupSiteAll(self,save):
        sites = public.M('sites').field('name').select()
        for site in sites:
            try:
                self.backupSite(site['name'],save)
            except:pass


    def backupDatabaseAll(self,save):
        databases = public.M('databases').field('name').select()
        for database in databases:
            try:
                self.backupDatabase(database['name'],save)
            except:pass


    #删除数据库到回收站
    def BackupToRecycleBin(self,name):
        info = public.get_mysql_info()
        backup_name = name + '_backup'
        dstPath = '{}/data/{}'.format(info['path'],backup_name)
        if not os.path.exists(dstPath): return False

        root = public.M('config').where('id=?',(1,)).getField('mysql_root');
        backup_path = "{}/Recycle_bin/BTDB_{}_t_{}".format(public.get_soft_path(),name,time.time())

        backup_shell = "{}/bin/mysqldump.exe -R -E --force --hex-blob --opt --default-character-set={} -P{} -uroot -p{} -R {} > {}".format(info['path'],public.get_database_character(backup_name),info['port'],root,backup_name,backup_path)
        ret = public.ExecShell(backup_shell)
        result = ''.join(ret)
        if not result:
            print('备份成功.')
            public.rmdir(dstPath)



    def __get_zip_filename(self,item):


        filename = item.filename
        try:
            filename = item.filename.encode('cp437').decode('gbk')
        except:pass
        return filename

    #文件压缩
    def Zip(self,sfile,dfile) :
        try:
            import zipfile
            filelists = []
            path = sfile
            if os.path.isdir(sfile):
                self.GetFileList(sfile, filelists)
            else:
                path = os.path.dirname(sfile)
                filelists.append(sfile)

            exclude = []
            try:
                key = sys.argv[len(sys.argv) - 1]
                if len(key) == 32:
                     epath = public.GetConfigValue('setup_path') + '/cron/{}.exclude'.format(key)
                     if os.path.exists(epath):
                         exclude = public.readFile(epath).strip().split('\n')

            except : pass
            print('|---过滤目录',"、".join(exclude))

            f = zipfile.ZipFile(dfile,'w',zipfile.ZIP_DEFLATED)
            for item in filelists:
                try:
                    #item = item.encode('gbk').decode('utf8')
                    if not self.check_exclude(item,exclude):

                        f.write(item,item.replace(path,''))
                except Exception as ex:
                    print('|---过滤异常',item,public.get_error_info())
            f.close()
            return True
        except :
            return False

    #检查排除目录
    def check_exclude(self,filename,exclude):
        import re
        for x in exclude:
            if not x: continue
            if re.search(x.strip("*"),filename) or filename.find(x) >= 0:
                return True
        return False

    #获取所有文件列表
    def GetFileList(self,path, list):
        files = os.listdir(path)
        list.append(path)
        for file in files:
            if os.path.isdir(path + '/' + file):
                self.GetFileList(path + '/' + file, list)
            else:
                list.append(path + '/' + file)

if __name__ == "__main__":
    backup = backupTools()
    type = sys.argv[1]
    if type == 'site':
        if sys.argv[2].lower() == 'all':
             backup.backupSiteAll(sys.argv[3])
        else:

            backup.backupSite(sys.argv[2], sys.argv[3])
    elif type == 'path':
        backup.backupPath(sys.argv[2],sys.argv[3])
    elif type == 'database':
        if sys.argv[2].lower() == 'all':
            backup.backupDatabaseAll(sys.argv[3])
        else:
            backup.backupDatabase(sys.argv[2], sys.argv[3])
    elif type == 'db_recyclebin':
        backup.BackupToRecycleBin(sys.argv[2])
