#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Windows面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# +-------------------------------------------------------------------


import sys,os,public,time,json,cgi,re

try:
    from BTPanel import session,request,cache
except:pass

class files:
    download_list = None
    download_is_rm = None
    #检查敏感目录
    def CheckDir(self,path):
        path = path.replace('//','/');
        if path[-1:] == '/':
            path = path[:-1]

        data = public.M('config').where("id=?",('1',)).field('sites_path').find();
        root_path = data['sites_path']
        nDirs = ('',
                'C:/',
                'C:/Windows',
                'C:/Windows/System32',
                root_path,
                public.GetConfigValue('root_path'),
                public.GetConfigValue('logs_path'),
                public.GetConfigValue('setup_path'))


        for dir in nDirs:
            if(dir == path):
                return False
        return True

    def CheckFileName(self,filename):
        """
        检测文件名
        @filename 文件名
        @return bool
        """
        nots = ['\\','*','|','?','<','>','"',':','/']
        if filename.find('/') != -1: filename = filename.split('/')[-1]
        for n in nots:
            if n in filename: return False
        return True


    # 上传前检查文件是否存在
    def upload_file_exists(self,args):
        '''
            @name 上传前检查文件是否存在
            @author hwliang<2021-11-3>
            @param filename<string> 文件名
            @return dict
        '''
        filename = args.filename.strip()
        if not os.path.exists(filename):
            return public.returnMsg(False,'指定文件不存在')
        file_info = {}
        _stat = os.stat(filename)
        file_info['size'] = _stat.st_size
        file_info['mtime'] = int(_stat.st_mtime)
        file_info['isfile'] = os.path.isfile(filename)
        return public.returnMsg(True,file_info)

    #上传文件
    def UploadFile(self,get):
        from werkzeug.utils import secure_filename
        from flask import request

        if not os.path.exists(get.path): os.makedirs(get.path)
        f = request.files['zunfile']
        filename = os.path.join(get.path, f.filename)
        s_path = get.path
        if os.path.exists(filename):s_path = filename
        p_stat = os.stat(s_path)
        f.save(filename)

        public.WriteLog('TYPE_FILE','FILE_UPLOAD_SUCCESS',(filename,get['path']));
        return public.returnMsg(True,'FILE_UPLOAD_SUCCESS');

    def f_name_check(self,filename):
        '''
            @name 文件名检测2
            @author hwliang<2021-03-16>
            @param filename<string> 文件名
            @return bool
        '''
        f_strs = [';','&','<','>']
        for fs in f_strs:
            if filename.find(fs) != -1:
                return False
        return True

    # 上传文件2
    def upload(self, args):

        if not 'f_name' in args:
            args.f_name = request.form.get('f_name')
            args.f_path = request.form.get('f_path')
            args.f_size = request.form.get('f_size')
            args.f_start = request.form.get('f_start')

        if not self.f_name_check(args.f_name): return public.returnMsg(False,'文件名中不能包含特殊字符!')

        if args.f_path == '/':
            return public.returnMsg(False,'不能直接上传文件到系统根目录!')

        if args.f_name.find('./') != -1 or args.f_path.find('./') != -1:
            return public.returnMsg(False, '错误的参数')
        if not os.path.exists(args.f_path):
            os.makedirs(args.f_path, 493)
            if not 'dir_mode' in args or not 'file_mode' in args:
                #self.set_mode(args.f_path)
                pass

        save_path = os.path.join(args.f_path, args.f_name + '.' + str(int(args.f_size)) + '.upload.tmp')
        d_size = 0
        if os.path.exists(save_path):
            d_size = os.path.getsize(save_path)
        if d_size != int(args.f_start):
            return d_size
        f = open(save_path, 'ab')
        if 'b64_data' in args:
            import base64
            b64_data = base64.b64decode(args.b64_data)
            f.write(b64_data)
        else:
            upload_files = request.files.getlist("blob")
            for tmp_f in upload_files:
                f.write(tmp_f.read())
        f.close()
        f_size = os.path.getsize(save_path)
        if f_size != int(args.f_size):
            return f_size
        new_name = os.path.join(args.f_path, args.f_name)
        if os.path.exists(new_name):public.rmdir(new_name)
        os.renames(save_path, new_name)

        public.WriteLog('TYPE_FILE', 'FILE_UPLOAD_SUCCESS',(args.f_name, args.f_path))
        return public.returnMsg(True, '上传成功!')

    #名称输出过滤
    def xssencode(self,text):
        from html import escape
        list=['<','>']
        ret=[]
        for i in text:
            if i in list:
                i=''
            ret.append(i)
        str_convert = ''.join(ret)
        text2 = escape(str_convert, quote=True)
        return text2

    def __get_stat(self,filename,path = None):
        stat = os.stat(filename)
        accept = str(oct(stat.st_mode)[-3:]);
        mtime = str(int(stat.st_mtime))
        user = ''

        user = str(stat.st_uid)
        size = str(stat.st_size)
        link = '';
        if os.path.islink(filename): link = ' -> ' + os.readlink(filename)
        tmp_path = (path + '/').replace('//','/')
        if path and tmp_path != '/':filename = filename.replace(tmp_path,'')
        filename = public.format_path(filename)
        return filename + ';' + size + ';' + mtime+ ';' +accept+ ';' +user+ ';' +link

    #查询子目录
    def SearchFiles(self,get):
        if not hasattr(get,'path'): get.path = 'C:/wwwroot'
        if not os.path.exists(get.path): get.path = 'C:/BtSoft';
        search = get.search.strip().lower();
        my_dirs = []
        my_files = []
        count = 0
        max = 3000
        for d_list in os.walk(get.path):
            if count >= max: break;
            for d in d_list[1]:
                if count >= max: break;
                d = self.xssencode(d)
                if d.lower().find(search) != -1:
                    my_dirs.append(self.__get_stat(d_list[0] + '/' + d,get.path))
                    count += 1

            for f in d_list[2]:
                if count >= max: break;
                f = self.xssencode(f)
                if f.lower().find(search) != -1:
                    my_files.append(self.__get_stat(d_list[0] + '/' + f,get.path))
                    count += 1
        data = {}
        data['DIR'] = sorted(my_dirs)
        data['FILES'] = sorted(my_files)
        data['PATH'] = str(get.path)
        data['PAGE'] = self.get_page(len(my_dirs) + len(my_files),1,max,'GetFiles')['page']
        data['STORE'] = self.get_files_store(None)
        return data

    # 构造分页
    def get_page(self,count,p=1,rows=12,callback='',result='1,2,3,4,5,8'):
        import page
        from BTPanel import request
        page = page.Page();
        info = { 'count':count,  'row':rows,  'p':p, 'return_js':callback ,'uri':request.full_path}
        data = { 'page': page.GetPage(info,result),  'shift': str(page.SHIFT), 'row': str(page.ROW) }
        return data

    # 是否包含composer.json
    def is_composer_json(self,path):
        if os.path.exists(path + '/composer.json'):
            return '1'
        return '0'

    def check_file_sort(self,sort):
        """
        @校验排序字段
        """
        slist = ['filename','size','mtime','accept','ext','type']
        if sort in slist: return sort
        return 'filename'


    def __get_file_list(self,get):
        """
        排序文件列表
        """
        path = get.path
        sort = None
        if hasattr(get, 'sort'):
            sort = self.check_file_sort(get.sort)
            reverse = False
            if hasattr(get, 'reverse'):
                if int(get.reverse): reverse = True
        else:
            get.sort = 'filename'
            sort = get.sort
            reverse = True

        nlist = []
        if sort:
            for filename in os.listdir(path):
                sort_key = 1
                sort_val = None
                filePath = '/'.join((path,filename))
                if sort == 'filename':
                    sort_key = 0
                elif sort == 'size':
                    try:
                        sort_val = os.stat(filePath).st_size
                    except :sort_val = 0
                elif sort == 'mtime':
                    sort_val = int(os.stat(filePath).st_mtime)
                elif sort == 'accept':
                    sort_val = int(oct(os.stat(filePath).st_mode)[-3:])
                elif sort == 'ext':
                    sort_val = ''
                    if os.path.isdir(filePath):
                        tmps = os.path.splitext(filePath)
                        if len(tmps) > 1: sort_val = tmps[1].strip('.')
                elif sort == 'type':
                    sort_val = 'file'
                    if os.path.isdir(filePath): sort_val = 'dir'

                nlist.append((filename,sort_val))

            nlist = sorted(nlist, key=lambda x: x[sort_key], reverse=reverse)
            return nlist
        else:
            for filename in os.listdir(path):
                nlist.append((filename,))
            return nlist

    def get_dir_sort(self,get, nlist):
        """
        二次排序目录
        """
        sort = None
        if hasattr(get, 'sort'):
            sort = self.check_file_sort(get.sort)
            reverse = False
            if hasattr(get, 'reverse'):
                if int(get.reverse): reverse = True

            slist = [];dir_list = []; file_list = []
            for file_obj in nlist:
                if file_obj['type'] == 'dir':
                    dir_list.append(file_obj)
                else:
                    file_list.append(file_obj)

            dir_list = sorted(dir_list, key=lambda x: x[sort], reverse=reverse)
            file_list = sorted(file_list, key=lambda x: x[sort], reverse=reverse)

        tmp_dirs = []; tmp_files = []

        for file_obj in dir_list:
            if file_obj['top']:
                tmp_dirs.insert(0,file_obj)
            else:
                tmp_dirs.append(file_obj)

        for file_obj in file_list:
            if file_obj['top']:
                tmp_files.insert(0,file_obj)
            else:
                tmp_files.append(file_obj)

        if reverse:
            slist = tmp_dirs + tmp_files
        else:
            slist = tmp_files + tmp_dirs

        return slist


    def __get_stat_list(self,filename,path = None):
        filePath = filename.replace('\\','/')
        f_obj = {}
        f_obj['link'] = ''
        f_obj['filename'] = filePath
        stat = os.stat(filePath)
        f_obj['user'] = ''
        f_obj['shell'] = False
        if not 'ext' in f_obj: f_obj['ext'] = ''
        if not 'size' in f_obj: f_obj['size'] = stat.st_size
        if not 'ps' in f_obj: f_obj['ps'] = self.get_file_ps(filePath)
        if not 'mtime' in f_obj: f_obj['mtime'] = int(stat.st_mtime)
        if not 'accept' in f_obj: f_obj['accept'] = int(oct(stat.st_mode)[-3:])
        if not 'down_id' in f_obj: f_obj['down_id'] = int(self.get_download_id(filePath))
        if not 'composer' in f_obj: f_obj['composer'] = int(self.is_composer_json(filePath))
        if not 'caret' in f_obj: f_obj['caret'] = self.check_caret(filePath)

        if not 'ps' in f_obj: f_obj['ps'] = self.get_file_ps(filePath)

        if not 'type' in f_obj:
            f_obj['type'] = 'file'
            if os.path.isdir(filePath):
                f_obj['type'] = 'dir'

        if f_obj['type'] == 'file':
            tmps = os.path.splitext(filePath)
            if len(tmps) > 1: f_obj['ext'] = tmps[1].strip('.')

        return f_obj

    #查询子目录
    def SearchFilesList(self,get):
        if not hasattr(get,'path'): get.path = 'C:/wwwroot'
        if not os.path.exists(get.path): get.path = 'C:/BtSoft'
        search = get.search.strip().lower()
        nlist = []
        count = 0
        max = 3000
        for d_list in os.walk(get.path):
            if count >= max: break
            for d in d_list[1]:
                if count >= max: break
                d = self.xssencode(d).replace('\\','/')

                if d.lower().find(search) != -1:
                    nlist.append(self.__get_stat_list(d_list[0] + '/' + d,get.path))
                    count += 1

            for f in d_list[2]:
                if count >= max: break
                f = self.xssencode(f).replace('\\','/')
                if f.lower().find(search) != -1:
                    nlist.append(self.__get_stat_list(d_list[0] + '/' + f,get.path))
                    count += 1
        data = {}

        data['LIST'] = sorted(nlist,key=lambda x:x['filename'])
        data['PATH'] = str(get.path)
        data['PAGE'] = self.get_page(len(nlist),1,max,'GetFiles')['page']
        data['STORE'] = self.get_files_store(None)
        data['search_history'] = public.get_search_history('files','get_list')
        return data

    def GetDirList(self,get):
        """
        取目录列表
        """
        #处理Windows路径
        if not hasattr(get,'path') or not os.path.exists(get.path):
            get.path = None
            for x in ["D:/","E:/","F:/","G:/"]:
                if os.path.exists(x):
                    get.path = x
                    break
            if not get.path: get.path = "C:/"

        if get.path[1:] == ':': get.path = get.path + '/'

        if not os.path.isdir(get.path): get.path = os.path.dirname(get.path)

        nlist = []
        search = None
        if hasattr(get,'search'):
            search = get.search.strip().lower()
            public.set_search_history('files','get_list',search)

        if hasattr(get,'all'): return self.SearchFilesList(get)

        #包含分页类
        import page
        #实例化分页类
        page = page.Page()
        info = {}
        info['count'] = self.GetFilesCount(get.path,search);
        info['row']   = 100
        info['p'] = 1
        if hasattr(get,'p'):
            try:
                info['p']     = int(get['p'])
            except:
                info['p'] = 1

        info['uri']   = {}
        info['return_js'] = ''
        if hasattr(get,'tojs'):
            info['return_js']   = get.tojs
        if hasattr(get,'showRow'):
            info['row'] = int(get.showRow);

        #获取分页数据
        data = {}
        data['PAGE'] = page.GetPage(info,'1,2,3,4,5,6,7,8')

        i = 0
        n = 0
        scanner = self.get_scanners()
        topinfo = self.__get_topping_data()
        for arrs in self.__get_file_list(get):
            if search:
                if arrs[0].lower().find(search) == -1: continue;
            i += 1
            if n >= page.ROW: break
            if i < page.SHIFT: continue

            try:
                filePath = '{}/{}'.format(get.path,arrs[0])
                f_obj = {}
                f_obj['link'] = ''
                f_obj['filename'] = arrs[0]
                stat = os.stat(filePath)
                f_obj['user'] = ''
                f_obj['shell'] = False
                if not 'ext' in f_obj: f_obj['ext'] = ''
                if not 'size' in f_obj: f_obj['size'] = stat.st_size
                if not 'ps' in f_obj: f_obj['ps'] = self.get_file_ps(filePath)
                if not 'mtime' in f_obj: f_obj['mtime'] = int(stat.st_mtime)
                if not 'accept' in f_obj: f_obj['accept'] = int(oct(stat.st_mode)[-3:])
                if not 'down_id' in f_obj: f_obj['down_id'] = int(self.get_download_id(filePath))
                if not 'composer' in f_obj: f_obj['composer'] = int(self.is_composer_json(filePath))
                if not 'caret' in f_obj: f_obj['caret'] = self.check_caret(filePath)

                if not 'ps' in f_obj: f_obj['ps'] = self.get_file_ps(filePath)
                if not 'top' in f_obj: f_obj['top'] = self.__check_topping(filePath,topinfo)

                if not 'type' in f_obj:
                    f_obj['type'] = 'file'
                    if os.path.isdir(filePath):
                        f_obj['type'] = 'dir'

                if f_obj['type'] == 'file':
                    if filePath in scanner:
                        if scanner[filePath] == public.Md5(public.readFile(filePath)): f_obj['shell'] = True

                    tmps = os.path.splitext(filePath)
                    if len(tmps) > 1: f_obj['ext'] = tmps[1].strip('.')

                nlist.append(f_obj)
                n += 1
            except:
                print(public.get_error_info())
                continue

        nlist = self.get_dir_sort(get,nlist)
        data['LIST'] = nlist
        data['PATH'] = str(get.path).strip("/")
        data['STORE'] = self.get_files_store(None)
        data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl');
        if hasattr(get,'disk'):
            import system
            data['DISK'] = system.system().GetDiskInfo()

        data['dir_history'] = public.get_dir_history('files','GetDirList')
        data['search_history'] = public.get_search_history('files','get_list')
        public.set_dir_history('files','GetDirList',data['PATH'])
        return data


    #取文件/目录列表
    def GetDir(self,get):
        try:
            if not hasattr(get,'path') or not os.path.exists(get.path):
                get.path = None
                for x in ["D:/","E:/","F:/","G:/"]:
                    if os.path.exists(x):
                        get.path = x
                        break
                if not get.path: get.path = "C:/"

            if get.path[1:] == ':': get.path = get.path + '/'
            if not os.path.isdir(get.path):
                get.path = os.path.dirname(get.path)

            dirnames = []
            filenames = []

            scanner = self.get_scanners()
            print
            search = None
            if hasattr(get,'search'): search = get.search.strip().lower();
            if hasattr(get,'all'): return self.SearchFiles(get)

            #包含分页类
            import page
            #实例化分页类
            page = page.Page()
            info = {}
            info['count'] = self.GetFilesCount(get.path,search);
            info['row']   = 100
            info['p'] = 1
            if hasattr(get,'p'):
                try:
                    info['p']     = int(get['p'])
                except:
                    info['p'] = 1

            info['uri']   = {}
            info['return_js'] = ''
            if hasattr(get,'tojs'):
                info['return_js']   = get.tojs
            if hasattr(get,'showRow'):
                info['row'] = int(get.showRow)

            #获取分页数据
            data = {}
            data['PAGE'] = page.GetPage(info,'1,2,3,4,5,6,7,8')

            i = 0
            n = 0

            for filename in os.listdir(get.path):
                if search:
                    if filename.lower().find(search) == -1: continue;
                i += 1
                if n >= page.ROW: break
                if i < page.SHIFT: continue

                try:
                    filePath = get.path+'/'+filename
                    link = ''
                    if os.path.islink(filePath):
                        filePath = os.readlink(filePath)
                        link = ' -> ' + filePath
                        if not os.path.exists(filePath): filePath = get.path + '/' + filePath
                        if not os.path.exists(filePath): continue

                    stat = os.stat(filePath)
                    accept = str(oct(stat.st_mode)[-3:])
                    mtime = str(int(stat.st_mtime))
                    user = ''

                    size = str(stat.st_size)
                    if os.path.isdir(filePath):
                        dirnames.append(filename+';'+size+';'+mtime+';'+accept+';'+user+';'+link+';0' + ';' +self.get_download_id(filePath)+';'+ self.is_composer_json(filePath));
                    else:
                        w_shell = 0
                        if filePath in scanner:
                            if scanner[filePath] == public.Md5(public.readFile(filePath)):
                                w_shell  = 1
                        filenames.append(filename+';'+size+';'+mtime+';'+accept+';'+user+';'+link+';'+str(w_shell)+';'+self.get_download_id(filePath));
                    n += 1
                except:
                    continue


            data['DIR'] = sorted(dirnames)
            data['FILES'] = sorted(filenames)
            data['PATH'] = str(get.path).strip("/")
            data['STORE'] = self.get_files_store(None)

            if hasattr(get,'disk'):
                import system
                data['DISK'] = system.system().GetDiskInfo()

            data['dir_history'] = public.get_dir_history('files','GetDir')
            public.set_dir_history('files','GetDir',data['PATH'])

            return data
        except Exception as ex:
            return public.returnMsg(False,str(ex))

    def GetFilesCount(self,path,search):
        """
        计算文件数量
        @path 文件路径
        @search 查询条件
        """
        i=0
        for name in os.listdir(path):
            if search:
                if name.lower().find(search) == -1: continue
            i += 1
        return i


    def CreateFile(self,get):
        """
        创建文件
        @get.path 文件路径

        """
        if not self.CheckFileName(get.path):
            return public.returnMsg(False,'文件名中不能包含特殊字符!');

        if os.path.exists(get.path):
            return public.returnMsg(False,'FILE_EXISTS')
        path = os.path.dirname(get.path)
        if not os.path.exists(path):
            os.makedirs(path)
        open(get.path,'w+').close()
        self.SetFileAccept(get.path)
        public.WriteLog('TYPE_FILE','FILE_CREATE_SUCCESS',(get.path,))
        return public.returnMsg(True,'FILE_CREATE_SUCCESS')


    def CreateDir(self,get):
        """
        创建目录
        @get.path 目录路径

        """
        if not self.CheckFileName(get.path):
            return public.returnMsg(False,'目录名中不能包含特殊字符!');
        if os.path.exists(get.path):
            return public.returnMsg(False,'DIR_EXISTS')
        os.makedirs(get.path)
        self.SetFileAccept(get.path);
        public.WriteLog('TYPE_FILE','DIR_CREATE_SUCCESS',(get.path,))
        return public.returnMsg(True,'DIR_CREATE_SUCCESS')


    def DeleteDir(self,get) :
        """
        删除目录
        """
        if not hasattr(get, 'path'):
            return public.returnMsg(False,'DIR_NOT_EXISTS')

        if not os.path.exists(get.path):
            return public.returnMsg(False,'DIR_NOT_EXISTS')

        #检查是否敏感目录
        if not self.CheckDir(get.path):
            return public.returnMsg(False,'FILE_DANGER');

        if hasattr(get,'empty'):
            if not self.delete_empty(get.path): return public.returnMsg(False,'DIR_ERR_NOT_EMPTY');

        if os.path.exists('data/recycle_bin.pl'):
            if self.Mv_Recycle_bin(get): return public.returnMsg(True,'DIR_MOVE_RECYCLE_BIN');

        import shutil
        try:
            shutil.rmtree(get.path)
        except :
            public.clear_is_read(get.path)
            shutil.rmtree(get.path)

        public.WriteLog('TYPE_FILE','DIR_DEL_SUCCESS',(get.path,))
        return public.returnMsg(True,'DIR_DEL_SUCCESS')


    def delete_empty(self,path):
        """
        删除空目录
        """
        for files in os.listdir(path):
            return False
        return True

    def DeleteFile(self,get):
        """
        删除文件
        @get.path 文件路径
        """
        if not os.path.exists(get.path):
            return public.returnMsg(False,'FILE_NOT_EXISTS')

        if os.path.exists('data/recycle_bin.pl') :
            if self.Mv_Recycle_bin(get):
                return public.returnMsg(True,'FILE_MOVE_RECYCLE_BIN');
        try:
            os.remove(get.path)
        except :
            public.ExecShell("attrib -r {} /s /d".format(public.to_path(get.path)))
            os.remove(get.path)

        ps_key = public.md5(get.path)
        f_key = '{}/data/files_ps/{}'.format(public.get_panel_path(),ps_key)
        if os.path.exists(f_key): os.remove(f_key)

        public.WriteLog('TYPE_FILE','FILE_DEL_SUCCESS',(get.path,))
        return public.returnMsg(True,'FILE_DEL_SUCCESS')


    def Mv_Recycle_bin(self,get):
        """
        移动到回收站
        """
        rPath = os.getenv("BT_SETUP") + '/Recycle_bin/'
        if not os.path.exists(rPath): os.makedirs(rPath)
        rFile = rPath + get.path.replace('/','_bt_').replace(':','_m_') + '_t_' + str(time.time());
        try:
            import shutil
            public.move(get.path, rFile)

            ps_key = public.md5(get.path)
            f_key = '{}/data/files_ps/{}'.format(public.get_panel_path(),ps_key)
            if os.path.exists(f_key): os.remove(f_key)

            public.WriteLog('TYPE_FILE','FILE_MOVE_RECYCLE_BIN',(get.path,))
            return True;
        except:
            public.WriteLog('TYPE_FILE','FILE_MOVE_RECYCLE_BIN_ERR',(get.path,))
            return False;

    def Re_Recycle_bin(self,get):
        """
        从回收站恢复
        """
        rPath = os.getenv("BT_SETUP") + '/Recycle_bin/'
        dFile = get.path.replace('_bt_','/').replace('_m_',':').split('_t_')[0];
        get.path = rPath + get.path
        if dFile.find('BTDB_') != -1:
            import database;
            return database.database().RecycleDB(get.path);

        import shutil
        public.move(get.path, dFile)
        public.WriteLog('TYPE_FILE','FILE_RE_RECYCLE_BIN',(dFile,))
        return public.returnMsg(True,'FILE_RE_RECYCLE_BIN');


    def Get_Recycle_bin(self,get):
        """
        获取回收站信息
        """
        rPath = os.getenv("BT_SETUP") + '/Recycle_bin/'
        if not os.path.exists(rPath): os.makedirs(rPath)
        data = {};
        data['dirs'] = [];
        data['files'] = [];
        data['status'] = os.path.exists('data/recycle_bin.pl');
        data['status_db'] = os.path.exists('data/recycle_bin_db.pl');
        for file in os.listdir(rPath):
            try:
                tmp = {};
                fname = rPath + file;
                tmp1 = file.split('_bt_');
                tmp2 = tmp1[len(tmp1)-1].split('_t_');
                tmp['rname'] = file;
                tmp['dname'] = file.replace('_bt_','/').replace('_m_',':').split('_t_')[0];
                tmp['name'] = tmp2[0];
                tmp['time'] = int(float(tmp2[1]));
                if os.path.islink(fname):
                    filePath = os.readlink(fname);
                    link = ' -> ' + filePath;
                    if os.path.exists(filePath):
                        tmp['size'] = os.path.getsize(filePath);
                    else:
                        tmp['size'] = 0;
                else:
                    tmp['size'] = os.path.getsize(fname);
                if os.path.isdir(fname):
                    data['dirs'].append(tmp);
                else:
                    data['files'].append(tmp);
            except:
                continue;
        return data;

    #彻底删除
    def Del_Recycle_bin(self,get):
        rPath = os.getenv("BT_SETUP") + '/Recycle_bin/'
        dFile = get.path.split('_t_')[0];
        if dFile.find('BTDB_') != -1:
            import database;
            return database.database().DeleteTo(rPath+get.path);
        if not self.CheckDir(rPath + get.path):
            return public.returnMsg(False,'FILE_DANGER');

        public.clear_is_read(rPath + get.path)
        if not os.path.exists(rPath + get.path): public.returnMsg(False,'操作失败，文件不存在.');

        if os.path.isdir(rPath + get.path):
            import shutil
            shutil.rmtree(rPath + get.path);
        else:
            os.remove(rPath + get.path);

        tfile = get.path.replace('_bt_','/').split('_t_')[0];
        public.WriteLog('TYPE_FILE','FILE_DEL_RECYCLE_BIN',(tfile,));
        return public.returnMsg(True,'FILE_DEL_RECYCLE_BIN',(tfile,));

    def Close_Recycle_bin(self,get):
        """
        清空回收站
        """
        rPath = os.getenv("BT_SETUP") + '/Recycle_bin/'
        import database,shutil;
        rlist = os.listdir(rPath)
        i = 0;
        l = len(rlist);
        for name in rlist:
            i += 1;
            path = rPath + name;
            public.writeSpeed(name,i,l);
            if name.find('BTDB_') != -1:
                database.database().DeleteTo(path);
                continue;
            public.clear_is_read(path)
            if os.path.isdir(path):
                shutil.rmtree(path,True);
            else:
                os.remove(path);
        public.writeSpeed(None,0,0);
        public.WriteLog('TYPE_FILE','FILE_CLOSE_RECYCLE_BIN');
        return public.returnMsg(True,'FILE_CLOSE_RECYCLE_BIN');



    def Recycle_bin(self,get):
        """
        回收站开关
        """
        c = 'data/recycle_bin.pl';
        if hasattr(get,'db'):
            c = 'data/recycle_bin_db.pl';

        if os.path.exists(c):
            os.remove(c)
            public.WriteLog('TYPE_FILE','FILE_OFF_RECYCLE_BIN');
            return public.returnMsg(True,'FILE_OFF_RECYCLE_BIN');
        else:
            public.writeFile(c,'True');
            public.WriteLog('TYPE_FILE','FILE_ON_RECYCLE_BIN');
            return public.returnMsg(True,'FILE_ON_RECYCLE_BIN');

    def CopyFile(self,get) :
        """
        复制文件
        @get.sfile 源文件
        @get.dfile 目标文件
        """
        if not os.path.exists(get.sfile):
            return public.returnMsg(False,'FILE_NOT_EXISTS')

        if os.path.isdir(get.sfile):
            return self.CopyDir(get)

        import shutil
        shutil.copyfile(get.sfile, get.dfile)
        public.WriteLog('TYPE_FILE','FILE_COPY_SUCCESS',(get.sfile,get.dfile))
        return public.returnMsg(True,'FILE_COPY_SUCCESS')


    def CopyDir(self,get):
        """
        复制目录
        @get.sfile 源目录
        @get.dfile 目标目录
        """
        if not os.path.exists(get.sfile):
            return public.returnMsg(False,'DIR_NOT_EXISTS');

        if os.path.exists(get.dfile):
            return public.returnMsg(False,'DIR_EXISTS');
        import shutil

        public.copytree(get.sfile, get.dfile)
        public.WriteLog('TYPE_FILE','DIR_COPY_SUCCESS',(get.sfile,get.dfile))
        return public.returnMsg(True,'DIR_COPY_SUCCESS')



    def MvFile(self,get):
        """
        移动文件或目录
        @get.sfile 源目录
        @get.dfile 目标目录
        """
        if not self.CheckFileName(get.dfile):
            return public.returnMsg(False,'文件名中不能包含特殊字符!');

        #if os.path.exists(get.dfile):
        #    return public.returnMsg(False,'操作失败，目标文件已存在!');

        if not os.path.exists(get.sfile):
            return public.returnMsg(False,'FILE_NOT_EXISTS')

        if not self.CheckDir(get.sfile):
            return public.returnMsg(False,'FILE_DANGER');

        public.move(get.sfile,get.dfile)
        if hasattr(get,'rename'):
            public.WriteLog('TYPE_FILE','[%s]重命名为[%s]' % (get.sfile,get.dfile))
            return public.returnMsg(True,'重命名成功!')
        else:
            public.WriteLog('TYPE_FILE','MOVE_SUCCESS',(get.sfile,get.dfile))
            return public.returnMsg(True,'MOVE_SUCCESS')


    def CheckExistsFiles(self,get):
        """
        检查文件是否存在
        """
        data = [];
        try:
            filesx = [];
            if not hasattr(get,'filename'):
                if not 'selected' in session: return []
                filesx = json.loads(session['selected']['data']);
            else:
                filesx.append(get.filename);

            for fn in filesx:
                if fn == '.': continue
                filename = get.dfile + '/' + fn;
                if os.path.exists(filename):
                    tmp = {}
                    stat = os.stat(filename)
                    tmp['filename'] = fn;
                    tmp['size'] = os.path.getsize(filename);
                    tmp['mtime'] = str(int(stat.st_mtime));
                    data.append(tmp);
        except : pass
        return data;

    def GetFileBody(self,get) :
        """
        获取文件内容
        """
        self.check_stop_page(get)
        if not os.path.exists(get.path):
            PANEL_ERR_LOG=re.findall("panelError.log",get.path)
            if PANEL_ERR_LOG:
                return public.returnMsg(False,'当前面板错误日志为空！')
            if get.path.find('rewrite') == -1:
                return public.returnMsg(False,'FILE_NOT_EXISTS',(get.path,))
            public.writeFile(get.path,' ');

        if os.path.getsize(get.path) > 2097152: return public.returnMsg(False,u'不能在线编辑大于2MB的文件!');
        if not os.path.isfile(get.path): return public.returnMsg(False,'这不是一个文件!')

        fp = open(get.path,'rb')
        data = {}
        data['status'] = True

        srcBody = b""
        if fp:
            srcBody = fp.read()
            fp.close()
            encoding,srcBody = public.decode_data(srcBody)
            if not encoding:  return public.returnMsg(False, u'文件编码不被兼容，无法正确读取文件!')

            data['encoding'] = encoding
            data['data'] = srcBody
            data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
            data['historys'] = self.get_history(get.path)
            data['caret'] = self.check_caret(get.path)
            #data['auto_save'] = self.get_auto_save(get.path)
            return data;

    def SaveFileBody(self,get):
        """
        保存文件
        """
        if not hasattr(get, 'path'): return public.returnMsg(False,'参数传递错误.')

        if not os.path.exists(get.path):
            if get.path.find('.htaccess') == -1:
                return public.returnMsg(False,'FILE_NOT_EXISTS')


        his_path = public.GetConfigValue('setup_path') + '/backup/file_history/'
        if not os.path.exists(his_path): os.makedirs(his_path)

        if get.path.find(his_path) != -1: return public.returnMsg(False,'不能直接修改历史副本!')

        data = get.data;
        if get.encoding == 'ascii':get.encoding = 'utf-8';

        self.save_history(get.path)

        is_user_ini = False
        if get.path.find('.user.ini') >= 0: is_user_ini = True

        if is_user_ini: public.ExecShell("attrib -r {}".format(public.to_path(get.path)))

        if 'st_mtime' in get:
            if not 'force' in get or get['force'] != '1':
                st_mtime = str(int(os.stat(get.path).st_mtime))
                if st_mtime != get['st_mtime']:
                    return public.returnMsg(False,'保存失败，{}文件发生改变，可能是该文件已经被其他人修改，请刷新内容后重新修改.'.format(public.format_path(get.path)))

        isConf = -1
        webserver = public.get_webserver()
        s_backup = 'data/backup_{}'.format(webserver)
        if webserver in ['nginx','apache']:
            isConf = get.path.find(webserver)
            if isConf != -1: public.writeFile(s_backup,public.readFile(get.path))


        fp = open(get.path,'wb')
        data = data.encode(get.encoding)
        fp.write(data)
        fp.close()

        if isConf != -1:
            isError = public.checkWebConfig()
            if isError != True:
                public.writeFile(get.path,public.readFile(s_backup))
                return public.returnMsg(False, 'ERROR:<br><font style="color:red;">'+isError.replace("\n", '<br>')+'</font>')
            public.serviceReload()

        if is_user_ini: public.ExecShell("attrib +r {}".format(public.to_path(get.path)))
        self.add_history_records(public.format_path(get.path))
        public.WriteLog('TYPE_FILE','FILE_SAVE_SUCCESS',(get.path,));
        data = public.returnMsg(True,'FILE_SAVE_SUCCESS');
        data['historys'] = self.get_history(get.path)
        data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
        return data


    def save_history(self,filename):
        """
        保存历史副本
        @filename 文件路径
        """
        if os.path.exists('data/not_file_history.pl'):  return True

        his_path = public.GetConfigValue('setup_path') + '/backup/file_history/'
        if filename.find(his_path) != -1: return
        save_path = ( his_path + filename.replace(':','_bt_')).replace('//','/').replace('\\','/')

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

        his_list = sorted(os.listdir(save_path),reverse=True)
        num =  public.readFile('data/history_num.pl')
        if not num:
            num = 10
        else:
            num = int(num)
        d_num = len(his_list)
        is_write = True
        new_file_md5 = public.FileMd5(filename)
        for i in range(d_num):
            rm_file = save_path + '/' + his_list[i]
            if i == 0: #判断是否和上一份副本相同
                old_file_md5 = public.FileMd5(rm_file)
                if old_file_md5 == new_file_md5: is_write = False

            if i+1 >= num: #删除多余的副本
                if os.path.exists(rm_file): os.remove(rm_file)
                continue
        #写入新的副本
        if is_write: public.writeFile2(save_path + '/' + str(int(time.time())),public.readFile2(filename,'rb'),'wb+')


    def get_history(self,filename):
        """
        取历史副本
        @filename 文件路径
        """

        save_path = (public.GetConfigValue('setup_path') + '/backup/file_history/' + filename.replace(':','_bt_')).replace('//','/').replace('\\','/')

        if not os.path.exists(save_path): return []
        return sorted(os.listdir(save_path))



    def read_history(self,args):
        """
        读取指定历史副本
        """
        save_path = (public.GetConfigValue('setup_path') +'/backup/file_history/' + args.filename.replace(':','_bt_')).replace('//','/').replace('\\','/')
        args.path = save_path + '/' + args.history
        return self.GetFileBody(args)

    def re_history(self,args):
        """
        恢复指定历史副本
        """
        save_path = (public.GetConfigValue('setup_path')+ '/backup/file_history/' + args.filename.replace(':','_bt_')).replace('//','/').replace('\\','/')
        args.path = save_path + '/' + args.history
        if not os.path.exists(args.path): return public.returnMsg(False,'指定历史副本不存在!')
        import shutil
        shutil.copyfile(args.path,args.filename)
        return self.GetFileBody(args)

    def check_stop_page(self,get):
        """
        检查是否存在停止页
        """
        setup_path = (os.getenv('BT_SETUP') + '/stop/index.html').replace('\\','/')
        if get.path.find(setup_path) != -1:
            if not os.path.exists(setup_path):
                stopPath = os.getenv('BT_SETUP') + '/stop'
                if not os.path.exists(stopPath):
                    os.makedirs(stopPath)

                    get.filename = stopPath
                    get.access = 2032127
                    get.user = 'IIS_IUSRS'
                    self.SetFileAccess(get)

                    get.user = 'www'
                    self.SetFileAccess(get)
                    public.downloadFile(public.get_url() + "/win/panel/data/stop.html",setup_path)

    def Zip(self,get) :
        """
        文件压缩
        """
        filelists = []
        if get.sfile.find(',') == -1:
            #处理单目录
            path = get.path + '/' + get.sfile
            path = path.strip('/')
            if not os.path.exists(path): return public.returnMsg(False,'FILE_NOT_EXISTS');

            if os.path.isdir(path):
                self.GetFileList(path, filelists)
            else:
                filelists.append(path)
        else:
            #处理批量
            batch_list = get.sfile.split(',')
            for f in batch_list:
                if f:
                    path = get.path + '/' + f
                    path = path.strip('/')

                    if os.path.isdir(path):
                        self.GetFileList(path, filelists)
                    else:
                        filelists.append(path)


        import zipfile
        f = zipfile.ZipFile(get.dfile,'w',zipfile.ZIP_DEFLATED)
        for item in filelists:
            s_path = item.replace(get.path,'')
            if s_path: f.write(item,s_path)
        f.close()

        self.SetFileAccept(get.dfile);
        public.WriteLog("TYPE_FILE", 'ZIP_SUCCESS',(get.sfile,get.dfile));
        return public.returnMsg(True,'ZIP_SUCCESS')


    #获取文件列表
    def GetFileList(self,path, list):
        if os.path.exists(path):
            try:
                file_list = os.listdir(path)
                list.append(path)
                for file in file_list:
                    if os.path.isdir(path + '/' + file):
                        self.GetFileList(path + '/' + file, list)
                    else:
                        list.append(path + '/' + file)
            except : pass

    #文件解压
    def UnZip(self,get):
        if not os.path.exists(get.sfile):
            return public.returnMsg(False,'FILE_NOT_EXISTS');

        if not hasattr(get,'password'): get.password = '';
        if not os.path.exists(get.dfile) : os.makedirs(get.dfile)

        ext = os.path.splitext(get.sfile)[-1]
        if ext == '.zip':
            import zipfile
            zip_file = zipfile.ZipFile(get.sfile)
            for names in zip_file.namelist():
                zip_file.extract(names, get.dfile,get.password.encode('utf-8'))
            zip_file.close()

        if ext == '.war':
            import zipfile
            zip_file = zipfile.ZipFile(get.sfile)
            for names in zip_file.namelist():
                zip_file.extract(names, get.dfile,get.password.encode('utf-8'))
            zip_file.close()
        elif ext == '.rar':
            from unrar import rarfile
            rar = rarfile.RarFile(get.sfile)
            rar.extractall(path = get.dfile,pwd = get.password)
        elif  ext == '.gz':
            return public.returnMsg(False,'未识别压缩包格式!')

        public.WriteLog("TYPE_FILE", 'UNZIP_SUCCESS',(public.format_path(get.sfile), public.format_path(get.dfile)));
        return public.returnMsg(True,'UNZIP_SUCCESS');


    #获取文件/目录 权限信息
    def GetFileAccess(self,get):
        filename = get.filename

        if not os.path.exists(filename):
            return public.returnMsg(False,'文件不存在!')

        import win32security
        sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
        dacl = sd.GetSecurityDescriptorDacl()
        ace_count = dacl.GetAceCount()
        data = {}
        data['path'] = filename
        arrs = []
        for i in range(0, ace_count):
            try:
                temp = {}
                rev, access, usersid = dacl.GetAce(i)
                if len(rev)==2:
                    if access >= 1179785:
                        current = False
                        if rev[1] == 3: current = False
                        temp['current'] = current
                        temp['access'] = access
                        temp['user'],temp['group'],type = win32security.LookupAccountSid('', usersid)

                        is_true = False;
                        for user in arrs:
                            if user['user']==temp['user']:
                                is_true  = True
                                if access > user['access']:
                                    user['access'] = access
                        if not is_true: arrs.append(temp)
            except : pass
        data['list'] = arrs
        return data;


    #设置文件权限和所有者
    def SetFileAccess(self,get,all = '-R'):
        filename = get.filename
        access = int(get.access)

        level = 1;
        if hasattr(get,'level'): level = int(get.level)

        if not os.path.exists(filename):
            return public.returnMsg(False,'FILE_NOT_EXISTS')

        if filename in ['C:/','C:/Windows','C:/Windows/System32']:
            return public.returnMsg(False,'设置文件权限失败，系统目录禁止设置权限.')

        self.SetFileAccept(filename)

        #是否继承
        if level:
            if os.path.isdir(filename):
                list = []
                self.GetFileList(filename,list)
                for new_path in list: self.set_file_access(new_path,get.user,access)
            else:
                self.set_file_access(filename,get.user,access)
        else:
            self.set_file_access(filename,get.user,access)

        public.WriteLog('TYPE_FILE','FILE_ACCESS_SUCCESS',(get.filename,str(get.access),get.user))
        return public.returnMsg(True,'SET_SUCCESS')


    #删除User权限
    def SetFileAccept(self,filename):
        self.del_file_access(filename,"users")
        return True

    #删除目录权限
    def DelFileAccess(self,get):
        filename = get.filename
        level = 1;
        if hasattr(get,'level'): level = int(get.level)

        #是否继承
        if level:
            if os.path.isdir(filename):
                list = []
                self.GetFileList(filename,list)
                for new_path in list: self.del_file_access(new_path,get.user)
            else:
                self.del_file_access(filename,get.user)
        else:
            self.del_file_access(filename,get.user)
        return public.returnMsg(True,'删除【'+get.user+'】权限成功!')

    #设置文件权限
    def set_file_access(self,filename,user,access):
        try:
            import win32security
            sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
            dacl = sd.GetSecurityDescriptorDacl()
            ace_count = dacl.GetAceCount()

            for i in range(ace_count, 0,-1):
                try:
                    data = {}
                    data['rev'], data['access'], usersid = dacl.GetAce(i-1)
                    data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
                    if data['user'].lower() == user.lower(): dacl.DeleteAce(i-1) #删除旧的dacl
                except :
                    dacl.DeleteAce(i-1)
            try:
                userx, domain, type = win32security.LookupAccountName("", user)
            except :
                userx, domain, type = win32security.LookupAccountName("", 'IIS APPPOOL\\' + user)
            if access > 0:  dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, access, userx)

            sd.SetSecurityDescriptorDacl(1, dacl, 0)
            win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
        except:
            pass

    #删除文件权限
    def del_file_access(self,filename,user):
        import win32security
        sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
        dacl = sd.GetSecurityDescriptorDacl()
        if not dacl: return False
        ace_count = dacl.GetAceCount()

        for i in range(ace_count ,0 ,-1):
            try:
                data = {}
                data['rev'], data['access'], usersid = dacl.GetAce(i-1)
                data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
                if data['user'].lower() == user.lower():
                    dacl.DeleteAce(i-1)
            except :
                try:
                    #处理拒绝访问
                    dacl.DeleteAce(i-1)
                except : pass
        sd.SetSecurityDescriptorDacl(1, dacl, 0)
        win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
        return True

    #取目录大小
    def GetDirSize(self,get):
        total = 0
        try:
            filesize = 0
            fileList = []
            self.GetFileList(get.path,fileList)

            for filename in fileList:
                try:
                    if os.path.isfile(filename): filesize += os.path.getsize(filename)
                except :pass

            total = public.to_size(filesize)
        except :
            total = public.to_size(0)
        return total

    def CloseLogs(self,get):
        import shutil
        get.path = public.GetConfigValue('logs_path')
        if not os.path.exists(get.path): os.makedirs(get.path)

        os.system('del '+ public.to_path(public.GetConfigValue('logs_path') + '/*.log'))
        for x in os.listdir(get.path):
            try:
                if x.find("W3SVC") >=0:
                   shutil.rmtree(get.path+'/' + x,True)
            except :pass

        public.WriteLog('TYPE_FILE','SITE_LOG_CLOSE')
        get.path = public.GetConfigValue('logs_path')
        public.serviceReload()
        return self.GetDirSize(get)

    #批量操作
    def SetBatchData(self,get):

        if get.type == '1' or get.type == '2':
            session['selected'] = get
            return public.returnMsg(True,'FILE_ALL_TIPS')
        elif get.type == '3':
            for key in json.loads(get.data):
                try:
                    filename = get.path+'/' + key;
                    if not self.CheckDir(filename): return public.returnMsg(False,'FILE_DANGER');
                except:
                    continue;
            public.WriteLog('TYPE_FILE','FILE_ALL_ACCESS')
            return public.returnMsg(True,'FILE_ALL_ACCESS')
        else:
            import shutil
            isRecyle = os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1
            path = get.path
            get.data = json.loads(get.data)
            l = len(get.data);
            i = 0;
            for key in get.data:
                try:
                    filename = path + '/'+ key;
                    get.path = filename;
                    if not os.path.exists(filename): continue
                    i += 1;
                    public.writeSpeed(key,i,l);
                    if os.path.isdir(filename):
                        if not self.CheckDir(filename): return public.returnMsg(False,'FILE_DANGER');
                        if isRecyle:
                            self.Mv_Recycle_bin(get)
                        else:
                            shutil.rmtree(filename)
                    else:
                        if isRecyle:
                            self.Mv_Recycle_bin(get)
                        else:
                            os.remove(filename)
                except: continue;
                public.writeSpeed(None,0,0);
            public.WriteLog('TYPE_FILE','FILE_ALL_DEL')
            return public.returnMsg(True,'FILE_ALL_DEL')

    #批量粘贴
    def BatchPaste(self,get):
        import shutil
        if not self.CheckDir(get.path): return public.returnMsg(False,'FILE_DANGER');
        i = 0;

        if not 'selected' in session:  return public.returnMsg(False,'操作失败，请重新操作.');

        myfiles = json.loads(session['selected']['data'])
        l = len(myfiles);
        if get.type == '1':
            for key in myfiles:
                i += 1
                public.writeSpeed(key,i,l);
                #try:
                if sys.version_info[0] == 2:
                    sfile = session['selected']['path'] + '/' + key.encode('utf-8')
                    dfile = get.path + '/' + key.encode('utf-8')
                else:
                    sfile = session['selected']['path'] + '/' + key
                    dfile = get.path + '/' + key

                if not os.path.exists(sfile): continue

                if os.path.isdir(sfile):
                    public.copytree(sfile,dfile)
                else:
                    shutil.copyfile(sfile,dfile)
                #except:
                    #continue;
            public.WriteLog('TYPE_FILE','FILE_ALL_COPY',(session['selected']['path'],get.path))
        else:
            for key in myfiles:
                try:
                    i += 1
                    public.writeSpeed(key,i,l);
                    if sys.version_info[0] == 2:
                        sfile = session['selected']['path'] + '/' + key.encode('utf-8')
                        dfile = get.path + '/' + key.encode('utf-8')
                    else:
                        sfile = session['selected']['path'] + '/' + key
                        dfile = get.path + '/' + key
                    public.move(sfile,dfile)
                except:
                    continue;
            public.WriteLog('TYPE_FILE','FILE_ALL_MOTE',(session['selected']['path'],get.path))
        public.writeSpeed(None,0,0);
        errorCount = len(myfiles) - i
        del(session['selected'])
        return public.returnMsg(True,'FILE_ALL',(str(i),str(errorCount)));


    ##下载文件
    #def DownloadFile(self,get):

    #    import db,time
    #    isTask = 'data/panelTask.pl'
    #    if not 'filename' in get or not get['filename']: return public.returnMsg(False,'下载的文件名不能为空,请重新填写.')

    #    execstr = get.url +'|bt|'+get.path+'/'+get.filename
    #    sql = db.Sql()
    #    sql.table('tasks').add('name,type,status,addtime,execstr',('下载文件['+get.filename+']','download','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr))
    #    public.writeFile(isTask,'True')

    #    public.WriteLog('TYPE_FILE','FILE_DOWNLOAD',(get.url , get.path));
    #    return public.returnMsg(True,'FILE_DOANLOAD')


    def DownloadFile(self, get):
        import panelTask
        task_obj = panelTask.bt_task()
        task_obj.create_task('下载文件', 1, get.url, get.path + '/' + get.filename)

        public.WriteLog('TYPE_FILE', 'FILE_DOWNLOAD', (get.url, get.path))
        return public.returnMsg(True, 'FILE_DOANLOAD')


    #删除任务队列
    def RemoveTask(self,get):
        try:
            name = public.M('tasks').where('id=?',(get.id,)).getField('name');
            status = public.M('tasks').where('id=?',(get.id,)).getField('status');
            public.M('tasks').delete(get.id);
            if status == '-1':
                pass
        except:
            public.set_server_status('btTask','restart')
        return public.returnMsg(True,'PLUGIN_DEL');

    #重新激活任务
    def ActionTask(self,get):
        isTask = os.getenv("BT_PANEL") + '/data/panelTask.pl'
        public.writeFile(isTask,'True');
        return public.returnMsg(True,'PLUGIN_ACTION');

    #卸载软件
    def UninstallSoft(self,get):
        panel_path = os.getenv("BT_PANEL")

        execstr = public.get_run_python('%s && cd %s/install && [PYTHON] -u install_soft.py uninstall phplib %s %s' % (panel_path[0:2],panel_path,get.version,get.name))

        public.ExecShell(execstr)
        public.WriteLog('TYPE_SETUP','PLUGIN_UNINSTALL',(get.name,get.version));
        return public.returnMsg(True,"PLUGIN_UNINSTALL");

    #添加安装任务
    def InstallSoft(self,get):
        import db,time

        panel_path = panel_path = os.getenv("BT_PANEL")
        isTask = panel_path + '/data/panelTask.pl'

        execstr = public.get_run_python('%s && cd %s/install && [PYTHON] -u install_soft.py install phplib %s %s' % (panel_path[0:2],panel_path,get.version,get.name))
        sql = db.Sql()
        if hasattr(get,'id'):
            id = get.id;
        else:
            id = None;

        sql.table('tasks').add('id,name,type,status,addtime,execstr',(None,'安装['+get.name+'-'+get.version+']','execshell','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr))
        public.writeFile(isTask,'True')
        public.WriteLog('TYPE_SETUP','PLUGIN_ADD',(get.name,get.version));
        time.sleep(0.1);
        return public.returnMsg(True,'PLUGIN_ADD');

    #取任务队列进度
    def GetTaskSpeed(self,get):
        setup_path = public.GetConfigValue('setup_path')

        tempFile = setup_path + '/panel/data/panelExec.log'
        freshFile = setup_path + '/panel/data/panelFresh'
        import db
        find = db.Sql().table('tasks').where('status=? OR status=?',('-1','0')).field('id,type,name,execstr').find()
        if not len(find): return public.returnMsg(False,'当前没有任务队列在执行-2!')
        isTask = setup_path + '/panel/data/panelTask.pl'
        public.writeFile(isTask,'True');
        echoMsg = {}
        echoMsg['name'] = find['name']
        echoMsg['execstr'] = find['execstr']
        if find['type'] == 'download':
            try:
                tmp = public.GetNumLines(tempFile,20)
                if len(tmp) < 10:
                    return public.returnMsg(False,'当前没有任务队列在执行-3!')
                echoMsg['msg'] = tmp
                echoMsg['isDownload'] = True
            except:
                print(public.get_error_info())
                db.Sql().table('tasks').where("id=?",(find['id'],)).save('status',('0',))
                return public.returnMsg(False,'当前没有任务队列在执行-4!')
        else:
            echoMsg['msg'] = self.GetLastLine(tempFile,20)
            echoMsg['isDownload'] = False

        echoMsg['task'] = public.M('tasks').where("status!=?",('1',)).field('id,status,name,type').order("id asc").select()
        return echoMsg

    #取执行日志
    def GetExecLog(self,get):
        return self.GetLastLine(public.GetConfigValue('setup_path') + '/panel/data/panelExec.log',100);

    #读文件指定倒数行数
    def GetLastLine(self,inputfile,lineNum):
        result = public.GetNumLines(inputfile,lineNum)
        if len(result) < 1:
            return public.getMsg('TASK_SLEEP');
        return result

    #执行SHELL命令
    def ExecShell(self,get):
        disabled = ['vi', 'vim', 'top', 'passwd', 'su']
        get.shell = get.shell.strip()
        tmp = get.shell.split(' ')
        if tmp[0] in disabled:
            return public.returnMsg(False, 'FILE_SHELL_ERR', (tmp[0],))

        if get.shell.find('install/update6.sh|bash') >=0 :
            get.shell = public.get_run_python('[PYTHON] {}/tools.py update_to'.format(os.getenv("BT_PANEL")))

        shell = '{0} > {1} 2>&1 '.format(get.shell,os.getenv("BT_PANEL") + '/data/panelShell.pl');
        public.ExecShell(shell)
        return public.returnMsg(True, 'FILE_SHELL_EXEC')

    #取SHELL执行结果
    def GetExecShellMsg(self,get):

        fileName = os.getenv("BT_PANEL") + '/data/panelShell.pl';
        if not os.path.exists(fileName): return 'FILE_SHELL_EMPTY';
        stat = os.stat(fileName)
        status = False
        time.sleep(1)
        if time.time() - stat.st_mtime >= 1 :
            status = True
        return public.returnMsg(status, public.GetNumLines(fileName, 200))


    #文件搜索
    def GetSearch(self,get):
        if not os.path.exists(get.path): return public.returnMsg(False,'DIR_NOT_EXISTS');
        return public.ExecShell("find "+get.path+" -name '*"+get.search+"*'");

    #保存草稿
    def SaveTmpFile(self,get):
        save_path = os.getenv("BT_PANEL") + '/temp'
        if not os.path.exists(save_path): os.makedirs(save_path)
        get.path = os.path.join(save_path,public.Md5(get.path) + '.tmp')
        public.writeFile(get.path,get.body)
        return public.returnMsg(True,'已保存')

    #获取草稿
    def GetTmpFile(self,get):
        self.CleanOldTmpFile()
        save_path = os.getenv("BT_PANEL") + '/temp'
        if not os.path.exists(save_path): os.makedirs(save_path)
        src_path = get.path
        get.path = os.path.join(save_path,public.Md5(get.path) + '.tmp')
        if not os.path.exists(get.path): return public.returnMsg(False,'没有可用的草稿!')
        data = self.GetFileInfo(get.path)
        data['file'] = src_path
        if 'rebody' in get: data['body'] = public.readFile(get.path)
        return data

    #清除过期草稿
    def CleanOldTmpFile(self):
        if 'clean_tmp_file' in session: return True
        save_path = os.getenv("BT_PANEL") + '/temp'
        max_time = 86400 * 30
        now_time = time.time()
        for tmpFile in os.listdir(save_path):
            filename = os.path.join(save_path,tmpFile)
            fileInfo = self.GetFileInfo(filename)
            if now_time - fileInfo['modify_time'] > max_time: os.remove(filename)
        session['clean_tmp_file'] = True
        return True


    def set_rsync_data(self,data):
        public.writeFile('{}/data/file_rsync.json'.format(public.get_panel_path()),json.dumps(data))
        return True

    def get_rsync_data(self):
        data = {}
        path = '{}/data/file_rsync.json'.format(public.get_panel_path())
        try:
            if os.path.exists(path):
                data = json.loads(public.readFile(path))
        except :
            data = {}
        return data


    def add_files_rsync(self,get):
        """
        @添加数据同步标记
        path:目录名
        s_type:类型 send/recv
        """
        path = get.path
        s_type = get.s_type

        data = self.get_rsync_data()
        if not path in data: data[path] = {}

        data[path][s_type] = 1

        self.set_rsync_data(data)
        return public.returnMsg(True,'添加成功!')


    def get_store_data(self):
        data = {}
        path = 'data/file_store.json'
        try:
            if os.path.exists(path):
                data = json.loads(public.readFile(path))
        except :
            data = {}
        if not data:
            data['默认分类'] = []

        is_change = False
        try:
            for key in data.keys():
                nlist = []
                for sfile in data[key]:
                    if type(sfile) == str:
                        is_change = True
                        sdata = {'path':sfile,'ps':sfile,'name':os.path.basename(sfile),'type':'file'}
                        if os.path.isdir(sfile): sdata['type'] = 'dir'
                        nlist.append(sdata)
                if(len(nlist)) > 0: data[key] = nlist

            if is_change: self.set_store_data(data)
        except:
            pass
        return data

    def set_store_data(self,data):
        public.writeFile('data/file_store.json',json.dumps(data))
        return True

    #添加收藏夹分类
    def add_files_store_types(self,get):
        file_type = get.file_type
        data = self.get_store_data()
        if file_type in data:  return public.returnMsg(False,'请勿重复添加分类!')

        data[file_type] = []
        self.set_store_data(data)
        return public.returnMsg(True,'添加收藏夹分类成功!')


    #删除收藏夹分类
    def del_files_store_types(self,get):
        file_type = get.file_type
        if file_type == '默认分类': return public.returnMsg(False,'默认分类不可被删除!')
        data = self.get_store_data()
        del data[file_type]
        self.set_store_data(data)
        return public.returnMsg(True,'删除[' + file_type + ']成功!')

    #获取收藏夹
    def get_files_store(self,get):
        data = self.get_store_data()
        result = []
        for key in data:
            result.append({'name':key,'data':data[key]})
        return result

    #添加收藏夹
    def add_files_store(self,get):
        """
        添加收藏夹
        @get.path 收藏路径
        @get.ps 备注
        """
        file_type = get.file_type
        path = get.path
        ps = path
        if hasattr(get,'ps'):ps = get.ps

        if not os.path.exists(path):  return public.returnMsg(False,'文件或目录不存在!')
        if self.check_caret(path): return public.returnMsg(False,'请勿重复添加!')

        data = self.get_store_data()
        sdata = {'path':path,'ps':ps,'name':os.path.basename(path),'type':'file'}
        if os.path.isdir(path): sdata['type'] = 'dir'

        data[file_type].append(sdata)
        self.set_store_data(data)
        return public.returnMsg(True,'添加成功!')

    def check_caret(self,path):
        """
        检测文件是否已收藏
        @path 验证路径
        """
        data = self.get_store_data()
        try:
            for key in data.keys():
                for sdata in data[key]:
                    if path == sdata['path']:
                        return {'file_type':key, 'status':True }
        except:
            pass
        return False

    #删除收藏夹
    def del_files_store(self,get):
        file_type = get.file_type
        path = get.path

        data = self.get_store_data()
        if not file_type in data:  return public.returnMsg(False,'找不到此收藏夹分类!')

        for sdata in data[file_type]:
            if path == sdata['path']:
                data[file_type].remove(sdata)

        if len(data[file_type]) <= 0: data[file_type] = []

        self.set_store_data(data)
        return public.returnMsg(True,'删除成功!')

    #取指定文件信息
    def GetFileInfo(self,path):
        if not os.path.exists(path): return False
        stat = os.stat(path)
        fileInfo = {}
        fileInfo['modify_time'] = int(stat.st_mtime)
        fileInfo['size'] = os.path.getsize(path)
        return fileInfo

    #取目录大小
    def get_path_size(self,get):
        data = {}
        if hasattr(get, 'path'):
            data['path'] = get.path
            data['size'] = public.get_path_size(get.path)
            return data
        else:
            return public.returnMsg(False,'路径传递错误!')

    #恢复网站权限（仅适配apache下www权限）
    def re_webserver_access(self,get):
        user = 'www'

        data = public.M('config').where("id=?",('1',)).field('sites_path').find();
        #完全控制权限
        paths = ["C:/Temp",public.GetConfigValue('logs_path'),os.getenv("BT_SETUP") + '/' + public.get_webserver(),data['sites_path']]
        #只读权限
        flist = [  ]
        for x in paths: public.get_paths(x,flist)

        #批量设置只读权限

        get.user = user
        get.access = 1179785
        get.level = 0
        for f in flist:
            get.filename = f
            self.SetFileAccess(get)

        for f in paths:
            get.level = 0
            get.access = 2032127
            get.filename = f
            self.SetFileAccess(get)

        return public.returnMsg(True,'权限恢复成功，当前仅恢复Apache启动所需权限，网站权限需要手动恢复!')

    #获取木马扫描记录
    def get_scanners(self):
        scanner = {}
        try:
            scanner = json.loads(public.readFile('data/scanner.json'))
        except :pass
        return scanner

    #单文件木马扫描
    def file_webshell_check(self,get):
        if not 'filename' in get: return public.returnMsg(False, '文件不存在!')
        import webshell_check

        name = os.path.basename(get.filename.strip())
        if webshell_check.webshell_check().upload_file_url(get.filename.strip()):
            scanner = self.get_scanners()
            scanner[get.filename.strip()] = public.Md5(public.readFile(get.filename.strip()))
            public.writeFile('data/scanner.json',json.dumps(scanner))

            return public.returnMsg(False,' <span style="color:red">警告</span>,【{}】为webshell文件。'.format(name))
        else:
            return public.returnMsg(True, '【{}文件】 无风险'.format(name))

    #目录扫描木马
    def dir_webshell_check(self,get):
        if not 'path' in get: return public.returnMsg(False, '请输入有效目录!')
        path=get.path.strip()
        if os.path.exists(path):
            #启动消息队列
            panelPath = os.getenv('BT_PANEL')
            exec_shell = public.get_run_python('[PYTHON] {}/class/webshell_check.py dir {} mail'.format(panelPath,path))
            task_name = "扫描目录%s 的木马文件"%path
            import panelTask
            task_obj = panelTask.bt_task()
            task_obj.create_task(task_name, 0, exec_shell)
            return public.returnMsg(True, '正在启动木马查杀进程。详细信息会在面板安全日志中')


    # 构造分页
    def get_page(self,count,p=1,rows=12,callback='',result='1,2,3,4,5,8'):
        import page
        from BTPanel import request
        page = page.Page()
        info = { 'count':count,  'row':rows,  'p':p, 'return_js':callback ,'uri':request.full_path}
        data = { 'page': page.GetPage(info,result),  'shift': str(page.SHIFT), 'row': str(page.ROW) }
        return data


    #获取下载地址列表
    def get_download_url_list(self,get):
        my_table = 'download_token'
        count = public.M(my_table).count()

        if not 'p' in get:
            get.p = 1
        if not 'collback' in get:
            get.collback = ''
        data = self.get_page(count,int(get.p),12, get.collback)
        data['data'] = public.M(my_table).order('id desc').field('id,filename,token,expire,ps,total,password,addtime').limit(data['shift'] +','+ data['row']).select()
        return data
    #获取短列表
    def get_download_list(self):
        if self.download_list: return self.download_list
        my_table = 'download_token'
        data = public.M(my_table).field('id,filename,expire').select()
        self.download_list = data
        return data

    #获取id
    def get_download_id(self,filename):
        download_list = self.get_download_list()
        my_table = 'download_token'
        m_time = time.time()
        result = '0'
        for d in download_list:
            if filename == d['filename']:
                result = str(d['id'])
                break

            #清理过期和无效
            if self.download_is_rm: continue
            if not os.path.exists(d['filename']) or m_time > d['expire']:
                public.M(my_table).where('id=?',(d['id'],)).delete()
        #标记清理
        if not self.download_is_rm:
            self.download_is_rm = True
        return result

    #获取指定下载地址
    def get_download_url_find(self,get):
        try:
            if not 'id' in get: return public.returnMsg(False,'错误的参数!')
            id = int(get.id)
        except :
            return public.returnMsg(False,'错误的参数!')

        my_table = 'download_token'
        data = public.M(my_table).where('id=?',(id,)).find()
        if not data: return public.returnMsg(False,'指定地址不存在!')
        return data

    #删除下载地址
    def remove_download_url(self,get):
        if not 'id' in get: return public.returnMsg(False,'错误的参数!')
        id = int(get.id)
        my_table = 'download_token'
        public.M(my_table).where('id=?',(id,)).delete()
        return public.returnMsg(True,'删除成功!')

    #修改下载地址
    def modify_download_url(self,get):
        if not 'id' in get: return public.returnMsg(False,'错误的参数!')
        id = int(get.id)
        my_table = 'download_token'
        if not public.M(my_table).where('id=?',(id,)).count():
            return public.returnMsg(False,'指定地址不存在!')
        pdata = {}
        if 'expire' in get: pdata['expire'] = get.expire
        if 'password' in get: pdata['password'] = get.password
        if 'ps' in get: pdata['ps'] = get.ps
        public.M(my_table).where('id=?',(id,)).update(pdata)
        return public.returnMsg(True,'修改成功!')


    #生成下载地址
    def create_download_url(self,get):
        if not os.path.exists(get.filename):
            return public.returnMsg(False,'指定文件不存在!')
        my_table = 'download_token'
        mtime = int(time.time())
        pdata = {
            "filename": get.filename,               #文件名
            "token": public.GetRandomString(12),    #12位随机密钥，用于URL
            "expire": mtime + (int(get.expire) * 3600), #过期时间
            "ps":get.ps, #备注
            "total":0,  #下载计数
            "password":str(get.password), #提取密码
            "addtime": mtime #添加时间
        }
        if len(pdata['password']) < 4 and len(pdata['password']) > 0:
            return public.returnMsg(False,'提取密码长度不能小于4位')
        #更新 or 插入
        token = public.M(my_table).where('filename=?',(get.filename,)).getField('token')
        if token:
            return public.returnMsg(False,'已经分享过了!')
        else:
            id = public.M(my_table).insert(pdata)
            pdata['id'] = id

        return public.returnMsg(True,pdata)


    def get_file_ps(self,filename):
        '''
            @name 获取文件或目录备注
            @author hwliang<2020-10-22>
            @param filename<string> 文件或目录全路径
            @return string
        '''
        ps_path = 'data/files_ps'
        f_key1 = '/'.join((ps_path,public.md5(filename)))
        if os.path.exists(f_key1):
            return public.readFile(f_key1)

        f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename))))
        if os.path.exists(f_key2):
            return public.readFile(f_key2)
        return ''


    def set_file_ps(self,args):
        '''
            @name 设置文件或目录备注
            @author hwliang<2020-10-22>
            @param filename<string> 文件或目录全路径
            @param ps_type<int> 备注类型 0.完整路径 1.文件名称
            @param ps_body<string> 备注内容
            @return dict
        '''
        filename = args.filename.strip()
        ps_type = int(args.ps_type)
        ps_body = args.ps_body
        ps_path = 'data/files_ps'
        if not os.path.exists(ps_path):
            os.makedirs(ps_path,384)
        if ps_type == 1:
            f_name = os.path.basename(filename)
        else:
            f_name = filename
        ps_key = public.md5(f_name)

        f_key = '/'.join((ps_path,ps_key))
        if ps_body:
            public.writeFile(f_key,ps_body)
            public.WriteLog('文件管理','设置文件名[{}],备注为: {}'.format(f_name,ps_body))
        else:
            if os.path.exists(f_key):os.remove(f_key)
            public.WriteLog('文件管理','清除文件备注[{}]'.format(f_name))
        return public.returnMsg(True,'设置成功')

    #获取指定目录下的所有视频或音频文件
    def get_videos(self,args):
        path = args.path.strip();
        v_data = []
        if not os.path.exists(path): return v_data
        import mimetypes
        for fname in os.listdir(path):
            try:
                filename = os.path.join(path,fname)
                if not os.path.exists(filename): continue
                if not os.path.isfile(filename): continue
                v_tmp = {}
                v_tmp['name'] = fname
                v_tmp['type'] = mimetypes.guess_type(filename)[0]
                v_tmp['size'] = os.path.getsize(filename)
                if not v_tmp['type'].split('/')[0] in ['video']:
                    continue
                v_data.append(v_tmp)
            except : pass
        return sorted(v_data,key=lambda x:x['name'])



    def add_history_records(self,path):
        """
        添加最近打开记录
        @path 文件路径
        """
        spath = 'data/history_records.json'
        nlist = []
        try:
            if os.path.exists(spath): nlist = json.loads(public.readFile(spath))
        except :pass

        count = 0
        total = 0
        for sfile in nlist:
            if sfile['path'] == path:
                count = sfile['count']
                nlist.remove(sfile)
            total += 1
            if total > 200:  nlist.remove(sfile)

        data = {}
        data['path'] = path
        data['name'] = os.path.basename(path)
        data['last_time'] = int(time.time())
        data['count'] = count + 1
        nlist.insert(0,data)

        public.writeFile(spath,json.dumps(nlist))
        return True


    def get_history_records(self,get):
        """
        获取最近打开记录
        """
        spath = 'data/history_records.json'
        data = []
        try:
            if os.path.exists(spath): data = json.loads(public.readFile(spath))
        except :pass

        return data


    # 计算文件HASH
    def get_file_hash(self,args=None,filename=None):
        if not filename: filename = args.filename
        import hashlib
        md5_obj = hashlib.md5()
        sha1_obj = hashlib.sha1()
        f = open(filename,'rb')
        while True:
            b = f.read(8096)
            if not b :
                break
            md5_obj.update(b)
            sha1_obj.update(b)
        f.close()
        return {'md5':md5_obj.hexdigest(),'sha1':sha1_obj.hexdigest()}


    # 取历史副本
    def get_history_info(self, filename):
        try:
            save_path =(public.GetConfigValue('setup_path') + '/backup/file_history/' + filename.replace(':','_bt_')).replace('//','/').replace('\\','/')

            if not os.path.exists(save_path):
                return []
            result = []
            for f in  sorted(os.listdir(save_path)):
                f_name = (save_path + '/' + f).replace('//', '/')
                pdata = {}
                pdata['md5'] = public.FileMd5(f_name)
                f_stat = os.stat(f_name)
                pdata['st_mtime'] = int(f)
                pdata['st_size'] = f_stat.st_size
                pdata['history_file'] = f_name
                result.append(pdata)
            return result
        except:
            print(public.get_error_info())
            return []
    #获取文件扩展名
    def get_file_ext(self,filename):
        ss_exts = ['tar.gz','tar.bz2','tar.bz']
        for s in ss_exts:
            e_len = len(s)
            f_len = len(filename)
            if f_len < e_len: continue
            if filename[-e_len:] == s:
                return s
        if filename.find('.') == -1: return ''
        return filename.split('.')[-1]

    # 取指定文件属性
    def get_file_attribute(self,args):
        filename = args.filename.strip()
        if not os.path.exists(filename):
            return public.returnMsg(False,'指定文件不存在!')

        attribute = {}
        attribute['name'] = os.path.basename(filename)
        attribute['path'] = os.path.dirname(filename)
        f_stat = os.stat(filename)
        attribute['st_atime'] = int(f_stat.st_atime)   # 最后访问时间
        attribute['st_mtime'] = int(f_stat.st_mtime)   # 最后修改时间
        attribute['st_ctime'] = int(f_stat.st_ctime)   # 元数据修改时间/权限或数据者变更时间
        attribute['st_size'] = f_stat.st_size          # 文件大小(bytes)
        attribute['st_gid'] = f_stat.st_gid            # 用户组id
        attribute['st_uid'] = f_stat.st_uid            # 用户id
        attribute['st_nlink'] = f_stat.st_nlink        #  inode 的链接数
        attribute['st_ino'] = f_stat.st_ino            #  inode 的节点号
        attribute['st_mode'] = f_stat.st_mode          #  inode 保护模式
        attribute['st_dev'] = f_stat.st_dev            #  inode 驻留设备

        attribute['mode'] = str(oct(f_stat.st_mode)[-3:])         # 文件权限号
        attribute['md5'] = False                        # 文件MD5
        attribute['sha1'] = False                       # 文件sha1
        attribute['is_dir'] = os.path.isdir(filename)   # 是否为目录
        attribute['history'] = []
        attribute['md5'] = '大于100M或目录不计算'                        # 文件MD5
        attribute['sha1'] = '大于100M或目录不计算'                       # 文件sha1
        attribute['is_link'] = os.path.islink(filename)  # 是否为链接文件
        if attribute['is_link']:
            attribute['st_type'] = '链接文件'
        elif attribute['is_dir']:
            attribute['st_type'] = '文件夹'
        else:
             attribute['st_type'] = self.get_file_ext(filename)
        if f_stat.st_size < 1073741824 and not attribute['is_dir']:
            hash_info = self.get_file_hash(filename=filename)
            attribute['md5'] = hash_info['md5']
            attribute['sha1'] = hash_info['sha1']
            print('111111111')
            attribute['history'] = self.get_history_info(filename) # 历史文件
        return attribute


    def __get_php_bin(self,php_version=None):
        import shutil
        #2021-08-20 暂不支持80
        php_vs = ["80","81","74","73","72","71","70","56","55","54","53"]
        if php_version:
            if php_version != 'auto':
                if not php_version in php_vs: return ''
            else:
                php_version = None

        #判段兼容的PHP版本是否安装
        php_path =  public.get_soft_path('php')
        php_v = None
        for pv in php_vs:
            if php_version:
                if php_version != pv: continue
            php_bin = php_path + '/' + pv + "/php.exe"
            if os.path.exists(php_bin):
                php_v = pv
                break
        #如果没安装直接返回False
        if not php_v: return ''
        #处理PHP-CLI-INI配置文件
        php_ini = public.to_path('{}/tmp/composer_php_cli_{}.ini'.format(public.get_panel_path(),php_v))
        if not os.path.exists(php_ini):
            src_php_ini = public.get_soft_path('php/{}/php.ini'.format(php_v))

            shutil.copy(src_php_ini,php_ini)
            #解除所有禁用函数
            php_ini_body = public.readFile(php_ini)
            php_ini_body = re.sub(r"disable_functions\s*=.*","disable_functions = ",php_ini_body)
            public.writeFile(php_ini,php_ini_body)
        return public.to_path(php_path + '/' + php_v + '/php.exe -c ' + php_ini)

    # 安装composer
    def get_composer_bin(self):
        composer_bin = public.to_path('{}/script/composer.phar'.format(public.get_panel_path()))
        if not os.path.exists(composer_bin):
            return False
        return composer_bin

    # 执行composer
    def exec_composer(self,get):
        #准备执行环境
        composer_bin = self.get_composer_bin()
        if not composer_bin:
            return public.returnMsg(False,'没有找到可用的composer!')

        #取执行PHP版本
        php_version = None
        if 'php_version' in get:
            php_version = get.php_version
        php_bin = self.__get_php_bin(php_version)

        if not php_bin:
            return public.returnMsg(False,'没有找到可用的PHP版本，或指定PHP版本未安装!')
        get.composer_cmd = get.composer_cmd.strip().replace("\\","/")
        if get.composer_cmd == '':
            if not os.path.exists(get.path + '/composer.json'):
                return public.returnMsg(False,'指定目录中没有找到composer.json配置文件!')

        log_file =public.to_path('{}/logs/composer.log'.format(public.get_panel_path()))

        #设置指定源
        if 'repo' in get:
            if get.repo != 'repos.packagist':
                public.ExecShell('{} {} config -g repo.packagist composer {}'.format(php_bin,composer_bin,get.repo))
            else:
                public.ExecShell('{} {} config -g --unset repos.packagist'.format(php_bin,composer_bin))
        #执行composer命令
        if not get.composer_cmd:
            composer_exec_str = '{} {} {} -vvv'.format(php_bin,composer_bin,get.composer_args)
        else:
            if get.composer_cmd.find('composer ') == 0 or get.composer_cmd.find('/usr/bin/composer ') == 0:
                composer_cmd = get.composer_cmd.replace('composer ','').replace('/usr/bin/composer ','')
                composer_exec_str = '{} {} {} -vvv'.format(php_bin,composer_bin,composer_cmd)
            else:
                composer_exec_str = '{} {} {} {} -vvv'.format(php_bin,composer_bin,get.composer_args,get.composer_cmd)

        public.rmdir(log_file)
        shell = public.to_path("{} && cd {} && composer_cmd >> {} 2>&1 & echo 'BT-Exec-Completed' >> {} 2>&1".format(get.path[:2],get.path,log_file,log_file))
        shell = shell.replace('composer_cmd',composer_exec_str)

        public.ExecShell(shell)
        public.WriteLog('Composer',"在目录：{}，执行composer {}".format(get.path,get.composer_args))
        return public.returnMsg(True,'命令已发送!')

    # 取composer版本
    def get_composer_version(self,get):
        composer_bin = self.get_composer_bin()
        if not composer_bin:
            return public.returnMsg(False,'没有找到可用的composer!')

        try:
            bs = str(public.readFile(composer_bin,'rb'))
            result = re.findall(r"const VERSION\s*=\s*.{0,2}'([\d\.]+)",bs)[0]
            if not result: raise Exception('empty!')
        except:
            raise public.PanelError('未识别的composer版本')

        data = public.returnMsg(True,result)
        if 'path' in get:
            import panelSite
            vers = panelSite.panelSite().GetPHPVersion(get)
            data['php_versions'] = []
            for x in vers:
                data['php_versions'].append(x)
            data['comp_json'] = True
            data['comp_lock'] = False
            if not os.path.exists(get.path + '/composer.json'):
                data['comp_json'] = '指定目录中没有找到composer.json配置文件!'
            if os.path.exists(get.path + '/composer.lock'):
                data['comp_lock'] = '指定目录中存在composer.lock文件,请删除后再执行!'
        return data

    # 升级composer版本
    def update_composer(self,get):
        composer_bin = self.get_composer_bin()
        if not composer_bin:
            return public.returnMsg(False,'没有找到可用的composer!')
        php_bin = self.__get_php_bin()
        if not php_bin:  return public.returnMsg(False,'没有找到可用的PHP版本!')

        version1 = self.get_composer_version(get)['msg']
        composer_exec_str = '{} {} self-update -vvv'.format(php_bin,composer_bin)

        public.ExecShell(composer_exec_str)
        version2 = self.get_composer_version(get)['msg']
        if version1 == version2:
            msg = "当前已经是最新版本，无需升级!"
        else:
            msg = "升级composer从{}到{}".format(version1,version2)
            public.WriteLog('Composer',msg)
        return public.returnMsg(True,msg)



    def files_search(self,args):
        import panelSearch
        adad=panelSearch.panelSearch()
        return adad.get_search(args)

    def files_replace(self,args):
        import panelSearch
        adad=panelSearch.panelSearch()
        return adad.get_replace(args)

    def get_replace_logs(self,args):
        import panelSearch
        adad=panelSearch.panelSearch()
        return adad.get_replace_logs(args)



    def get_path_images(self,path):
        '''
            @name 获取目录的图片列表
            @param path 目录路径
            @return 图片列表
        '''
        image_list = []
        for fname in os.listdir(path):
            if fname.split('.')[-1] in ['png','jpeg','gif','jpg','bmp','ico']:
                image_list.append(fname)
        return ','.join(image_list)

    def get_images_resize(self,args):
        '''
            @name 获取指定图片的缩略图
            @author hwliang<2022-03-02>
            @param args<dict_obj>{
                "path": "", 图片路径
                "files": xx.png,aaa.jpg, 文件名称(不包含目录路径),如果files=*，则返回该目录下的所有图片
                "width": 50, 宽
                "heigth:50, 高
                "return_type": "base64" // base64,file
            }
            @return base64编码的图片 or file
        '''
        from PIL import Image
        from base64 import b64encode
        from io import BytesIO
        if args.files == '*':
            args.files = self.get_path_images(args.path)

        file_list = args.files.split(',')

        width = int(args.width)
        height = int(args.height)

        data = {}
        for fname in file_list:
            try:
                filename = os.path.join(args.path,fname)
                if not os.path.exists(filename): continue
                im  = Image.open(filename)
                im.thumbnail((width,height))
                out = BytesIO()
                im.save(out, im.format)
                out.seek(0)
                image_type = im.format.lower()
                mimetype = 'image/{}'.format(image_type)
                if args.return_type == 'base64':
                    b64_data = "data:{};base64,".format(mimetype) + b64encode(out.read()).decode('utf-8')
                    data[fname] = b64_data
                    out.close()
                else:
                    from flask import send_file
                    return send_file(out, mimetype=mimetype, cache_timeout=0)
            except:
                data[fname] = ''
        return public.return_data(True,data)



    # 取文件指定尾行数
    def GetNumLines(self,path, num, p=1,search = None):
        pyVersion = sys.version_info[0]
        max_len = 1024 * 128
        try:
            from html import escape
            if not os.path.exists(path): return ""
            start_line = (p - 1) * num
            count = start_line + num
            fp = open(path, 'rb')

            buf = ""
            fp.seek(-1, 2)
            if fp.read(1) == "\n": fp.seek(-1, 2)
            data = []
            total_len = 0
            b = True
            n = 0

            for i in range(count):
                while True:
                    newline_pos = str.rfind(str(buf), "\n")

                    pos = fp.tell()
                    if newline_pos != -1:
                        if n >= start_line:
                            line = buf[newline_pos + 1:]

                            is_res = True
                            if search:
                                is_res = False
                                if line.find(search) >= 0 or re.search(search,line):
                                    is_res = True

                            if not is_res: continue

                            line_len = len(line)
                            total_len += line_len
                            sp_len = total_len - max_len
                            if sp_len > 0:
                                line = line[sp_len:]
                            try:
                                data.insert(0, escape(line))
                            except:
                                pass
                        buf = buf[:newline_pos]
                        n += 1
                        break
                    else:
                        if pos == 0:
                            b = False
                            break
                        to_read = min(4096, pos)
                        fp.seek(-to_read, 1)
                        t_buf = fp.read(to_read)
                        if pyVersion == 3:
                            try:
                                if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8')
                            except:
                                try:
                                    if type(t_buf) == bytes: t_buf = t_buf.decode('gbk')
                                except:
                                    t_buf = str(t_buf)
                        buf = t_buf + buf
                        fp.seek(-to_read, 1)
                        if pos - to_read == 0:
                            buf = "\n" + buf
                    if total_len >= max_len: break
                if not b: break
            fp.close()
            result = "\n".join(data)

            if not result: raise Exception('null')
        except:
            print(public.get_error_info())
            result = ''
            if len(result) > max_len:
                result = result[-max_len:]

        try:
            try:
                result = json.dumps(result)
                return json.loads(result).strip()
            except:
                if pyVersion == 2:
                    result = result.decode('utf8', errors='ignore')
                else:
                    result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore")
            return result.strip()
        except:
            return ""

    def get_logs_info(self,get):
        """
        @查看日志
        @param get
            limit:每页显示条数
            file:日志文件
        """
        p = 1
        limit = 200
        search = None
        file = get.file
        if 'p' in get: limit = int(get.p)
        if 'limit' in get: limit = int(get.limit)
        if 'search' in get: search = get.search

        if not os.path.exists(file):
            return public.returnMsg(False,'请指定文件!')

        res = {}
        res['status'] = True
        res['data'] = self.GetNumLines(file,limit,p,search)

        res['md5'] = public.md5(res['data'])
        res['limit'] = limit

        if not cache.get(file+'_logs_info'):
            public.set_module_logs('files_get_logs_info','get_logs_info')
            cache.set(file+'_logs_info','1',86400)
        return res

    def __get_topping_data(self):
        """
        @获取置顶配置
        """
        data = {}
        conf_file = '{}/data/toping.json'.format(public.get_panel_path())
        try :
            if os.path.exists(conf_file):
                data = json.loads(public.readFile(conf_file))
        except:pass
        return data

    def __check_topping(self,filepath,top_info):
        """
        @name 检测文件或者目录是否置顶
        @param filepath: 文件路径
        """
        if filepath in top_info:
            return 1
        return 0





