#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
#-------------------------------------------------------------------
#角色说明：
#read：允许用户读取指定数据库
#readWrite：允许用户读写指定数据库
#dbAdmin：允许用户在指定数据库中执行管理函数，如索引创建、删除，查看统计或访问system.profile
#userAdmin：允许用户向system.users集合写入，可以找指定数据库里创建、删除和管理用户
#clusterAdmin：只在admin数据库中可用，赋予用户所有分片和复制集相关函数的管理权限。
#readAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的读权限
#readWriteAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的读写权限
#userAdminAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的userAdmin权限
#dbAdminAnyDatabase：只在admin数据库中可用，赋予用户所有数据库的dbAdmin权限。
#root：只在admin数据库中可用。超级账号，超级权限

# sqlite模型
#------------------------------
import os,sys,re,json,shutil,psutil,time
from databaseModel.base import databaseBase
import public,panelMssql,pymongo
try:
    from BTPanel import session
except :pass


class panelMongoDB():

    __DB_PASS = None
    __DB_USER = None
    __DB_PORT = 27017
    __DB_HOST = '127.0.0.1'
    __DB_CONN = None
    __DB_ERR = None

    __DB_CLOUD = None
    def __init__(self):
        self.__config = self.get_options(None)

    def __Conn(self):

        if not self.__DB_CLOUD:
            path = '{}/data/mongo.root'.format(public.get_panel_path())
            if os.path.exists(path): self.__DB_PASS = public.readFile(path)
            self.__DB_PORT = int(self.__config['port'])

        try:
            self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS)
            self.__DB_CONN.admin.command({"listDatabases":1})
            return True
        except :
            try:
                self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS)
                self.__DB_CONN.admin.authenticate('root', self.__DB_PASS)
                return True
            except :
                self.__DB_ERR = public.get_error_info()
        return False


    def get_db_obj(self,db_name = 'admin'):
        """
        @获取连接对象
        """
        if not self.__Conn(): return self.__DB_ERR

        return self.__DB_CONN[db_name]

    def set_host(self,host,port,name,username,password,prefix = ''):
        self.__DB_HOST = host
        self.__DB_PORT = int(port)
        self.__DB_NAME = name
        if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME)
        self.__DB_USER = str(username)
        self._USER = str(username)
        self.__DB_PASS = str(password)
        self.__DB_PREFIX = prefix
        self.__DB_CLOUD = 1
        return self



    #获取配置文件
    def get_config(self,get):
        filename =  '{}/mongodb/config.conf'.format(public.get_soft_path())
        if os.path.exists(filename):
            return public.readFile(filename);
        return ""

    #获取配置项
    def get_options(self,get):
        options = ['port','bind_ip','logpath','dbpath','auth']
        data = {}
        conf = self.get_config(None)

        for opt in options:
            tmp = re.findall("\s+" +opt + "\s*=\s*(.+)",conf)
            if not tmp: continue;
            data[opt] = tmp[0]

        if not 'auth' in data:data['auth'] = False
        return data


class main(databaseBase):

    __conf_path = '{}/mongodb/config.conf'.format(public.get_soft_path())
    def __init__(self):
        pass


    def get_list(self,args):
        """
        @获取数据库列表
        @sql_type = sqlserver
        """
        return self.get_base_list(args, sql_type = 'mongodb')

    def GetCloudServer(self,args):
        '''
            @name 获取远程服务器列表
            @author hwliang<2021-01-10>
            @return list
        '''
        return self.GetBaseCloudServer(args)


    def AddCloudServer(self,args):
        '''
        @添加远程数据库
        '''
        return self.AddBaseCloudServer(args)

    def RemoveCloudServer(self,args):
        '''
        @删除远程数据库
        '''
        return self.RemoveBaseCloudServer(args)

    def ModifyCloudServer(self,args):
        '''
        @修改远程数据库
        '''
        return self.ModifyBaseCloudServer(args)

    #获取数据库列表
    def exists_databases(self,get):
        db_name = get
        if type(get) != str:db_name = get['db_name']

        db_obj = self.get_obj_by_sid(self.sid).get_db_obj('admin')
        data = db_obj.command({"listDatabases":1})
        if 'databases' in data:
            for x in data['databases']:
                if x['name'] == db_name:
                    return True
        return False

    def __set_auth_open(self,status):
        """
        @设置数据库密码访问开关
        @状态 status:1 开启，2：关闭
        """

        conf = public.readFile(self.__conf_path)
        if status:
            conf = re.sub('#auth\s+\=\s+true','auth = true',conf)
        else:
            conf = re.sub('\nauth\s+\=\s+true',"\n"+'#auth = true',conf)

        public.writeFile(self.__conf_path,conf)
        self.restart_services()

        return True


    def set_auth_status(self,get):
        """
        @设置密码认证状态
        @status int 0：关闭，1：开启
        """

        if public.get_server_status('mongodb') < 0:
            return public.returnMsg(False, '未安装mongodb数据库，请前往[软件商店]安装')

        status = int(get.status)
        path = '{}/data/mongo.root'.format(public.get_panel_path())
        if status:
            if hasattr(get,'password'):
                password = get['password'].strip()
                if not password or not re.search("^[\w@\.]+$", password):
                    return public.returnMsg(False, '数据库密码不能为空或带有特殊字符')

                if re.search('[\u4e00-\u9fa5]',password):
                    return public.returnMsg(False,'数据库密码不能为中文，请换个名称!')
            else:
                password = public.GetRandomString(16)
            self.__set_auth_open(0)

            _client = panelMongoDB().get_db_obj('admin')
            try:
                _client.command("dropUser", "root")
            except : pass

            _client.command("createUser", "root", pwd=password, roles=[
                {'role':'root','db':'admin'},
                {'role':'clusterAdmin','db':'admin'},
                {'role':'readAnyDatabase','db':'admin'},
                {'role':'readWriteAnyDatabase','db':'admin'},
                {'role':'userAdminAnyDatabase','db':'admin'},
                {'role':'dbAdminAnyDatabase','db':'admin'},
                {'role':'userAdmin','db':'admin'},
                {'role':'dbAdmin','db':'admin'}
               ])

            self.__set_auth_open(1)

            public.writeFile(path,password)
        else:
            if os.path.exists(path): os.remove(path)
            self.__set_auth_open(0)

        return public.returnMsg(True,'操作成功.')

    def restart_services(self):
        """
        @重启服务
        """
        public.set_server_status('mongodb','restart')
        return True

    def get_obj_by_sid(self,sid = 0,conn_config = None):
        """
        @取mssql数据库对像 By sid
        @sid 数据库分类，0：本地
        """
        if type(sid) == str:
            try:
                sid = int(sid)
            except :sid = 0

        if sid:
            if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find()
            db_obj = panelMongoDB()

            try:
                db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
            except Exception as e:
                raise public.PanelError(e)
        else:
            db_obj = panelMongoDB()
        return db_obj



    def get_local_auth(self,get):
        """
        @验证本地数据库是否需要密码
        """
        self.sid = get.get('sid/d',0)
        if self.sid != 0: return True

        conf = panelMongoDB().get_options(None)
        if conf['auth'] == 'true':
            return True
        return False

    def AddDatabase(self,args):
        """
        @添加数据库
        """

        username = ''
        password = ''
        auth_status = self.get_local_auth(args)
        data_name = args.name.strip()

        if self.sid <= 0 and public.get_server_status("mongodb") <= 0:
            return public.returnMsg(False,"Mongodb服务还未开启！")

        if not data_name:
            return public.returnMsg(False, "数据库名不能为空！")

        if auth_status:
            res = self.add_base_database(args)
            if not res['status']: return res

            data_name = res['data_name']
            username = res['username']
            password = res['data_pwd']
        else:
             username = data_name

        dtype = 'MongoDB'
        if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS');
        addTime = time.strftime('%Y-%m-%d %X',time.localtime())

        pid = 0
        if hasattr(args,'pid'): pid = args.pid

        if hasattr(args,'contact'):
            site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find()
            if site:
                pid = int(args.contact)
                args['ps'] = site['name']

        db_type = 0
        if self.sid: db_type = 2

        db_obj = self.get_obj_by_sid(self.sid).get_db_obj(data_name)
        db_obj.chat.insert_one({})
        if auth_status:
            db_obj.command("createUser", username, pwd=password, roles=[{'role':'dbOwner','db':data_name},{'role':'userAdmin','db':data_name}])

        public.set_module_logs('win_mongodb','AddDatabase',1)

        #添加入SQLITE
        public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype))
        public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,))
        return public.returnMsg(True,'ADD_SUCCESS')


    def DeleteDatabase(self,args):
        """
        @删除数据库
        """

        id = args['id']
        find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid,db_type').find();
        if not find: return public.returnMsg(False,'指定数据库不存在.')

        name = args['name']
        username = find['username'];

        db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(name)
        try:
            db_obj.command("dropUser", username)
        except :
            pass

        db_obj.command('dropDatabase')
        #删除SQLITE
        public.M('databases').where("id=?",(id,)).delete()
        public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(name,))
        return public.returnMsg(True, 'DEL_SUCCESS')


    def get_info_by_db_id(self,db_id):
        """
        @获取数据库连接详情
        @db_id 数据库id
        """
        find = public.M('databases').where("id=?" ,db_id).find()
        if not find: return False

        data = {
            'db_host':'127.0.0.1',
            'db_port':int(panelMongoDB().get_options(None)['port']),
            'db_user':find['username'],
            'db_password':find['password']
        }

        if int(find['sid']):
            conn_config = public.M('database_servers').where("id=?" ,find['sid']).find()

            data['db_host'] = conn_config['db_host']
            data['db_port'] = int(conn_config['db_port'])
        return data

        #导入
    def InputSql(self,args):

        name = args.name
        file = args.file

        find = public.M('databases').where("name=?",(name,)).find()
        if not find: return public.returnMsg(False,'数据库不存在!')

        get = public.dict_obj()
        get.sid = find['sid']

        info = self.get_info_by_db_id(find['id'])
        sql_dump = '{}/mongodb/bin/mongorestore.exe'.format(public.get_soft_path())
        if not os.path.exists(sql_dump): return public.returnMsg(False,'缺少备份工具，请先通过软件管理安装MongoDB!')

        if os.path.isfile(file): file = os.path.dirname(file)

        if self.get_local_auth(get):
            shell = "{} -h {} --port {} -u {} -p {} -d {} --drop {} ".format(sql_dump,info['db_host'],info['db_port'],info['db_user'],info['db_password'],find['name'] ,file)
        else:
            shell = "{} -h {} --port {} -d {} --drop {}".format(sql_dump,info['db_host'],info['db_port'],find['name'] ,file)
        public.ExecShell(shell)

        public.WriteLog("TYPE_DATABASE", '导入数据库[{}]成功'.format(name))
        return public.returnMsg(True, 'DATABASE_INPUT_SUCCESS')


    def ToBackup(self,args):
        """
        @备份数据库 id 数据库id
        """
        id = args['id']
        find = public.M('databases').where("id=?",(id,)).find()
        if not find: return public.returnMsg(False,'数据库不存在!')

        fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S',time.localtime())
        backupName = session['config']['backup_path'] + '/database/mongodb/' + fileName

        spath = os.path.dirname(backupName)
        if not os.path.exists(spath): os.makedirs(spath)

        get = public.dict_obj()
        get.sid = find['sid']
        info = self.get_info_by_db_id(id)

        sql_dump = '{}/mongodb/bin/mongodump.exe'.format(public.get_soft_path())
        if not os.path.exists(sql_dump): return public.returnMsg(False,'缺少备份工具，请先通过软件管理安装MongoDB!')

        if self.get_local_auth(get):
            if not find['password']:
                return public.returnMsg(False,'备份失败，密码为空，请修改密码后重新备份!')

            shell = "{}  -h {} --port {} -u {} -p {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],info['db_user'],info['db_password'],find['name'] ,backupName)
        else:
            shell = "{}  -h {} --port {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],find['name'] ,backupName)


        ret = public.ExecShell(shell)
        print(shell,spath)
        if not os.path.exists(backupName):
            return public.returnMsg(False,'数据库备份失败，文件不存在');

        sfile = '{}/{}/chat.bson'.format(backupName,find['name'])
        public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,sfile,0,time.strftime('%Y-%m-%d %X',time.localtime())))
        public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],))

        if os.path.getsize(sfile) < 1:
            return public.returnMsg(True, '备份执行成功，备份文件小于1b，请检查备份完整性.')
        else:
            return public.returnMsg(True, 'BACKUP_SUCCESS')

    def DelBackup(self,args):
        """
        @删除备份文件
        """
        return self.delete_base_backup(args)

    #同步数据库到服务器
    def SyncToDatabases(self,get):
        type = int(get['type'])
        n = 0
        sql = public.M('databases')
        if type == 0:
            data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('MongoDB',)).select()

            for value in data:
                if value['db_type'] in ['1',1]:
                    continue # 跳过远程数据库
                result = self.ToDataBase(value)
                if result == 1: n +=1
        else:
            import json
            data = json.loads(get.ids)
            for value in data:
                find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find()
                result = self.ToDataBase(find)
                if result == 1: n +=1

        return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),))

    #添加到服务器
    def ToDataBase(self,find):
        if find['username'] == 'bt_default': return 0
        if len(find['password']) < 3 :
            find['username'] = find['name']
            find['password'] = public.md5(str(time.time()) + find['name'])[0:10]
            public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username']))

        self.sid = find['sid']
        db_obj = self.get_obj_by_sid(self.sid).get_db_obj(find['name'])
        db_obj.chat.insert_one({})

        get = public.dict_obj()
        get.sid = self.sid
        if self.get_local_auth(get):
            try:
                db_obj.command("dropUser", find['username'])
            except :pass
            db_obj.command("createUser", find['username'], pwd=find['password'], roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}])
        return 1

    def SyncGetDatabases(self,get):
        """
        @从服务器获取数据库
        """
        n = 0;s = 0;
        db_type = 0
        self.sid = get.get('sid/d',0)
        if self.sid: db_type = 2

        data = self.get_obj_by_sid(self.sid).get_db_obj('admin').command({"listDatabases":1})

        sql = public.M('databases')
        nameArr = ['information_schema','performance_schema','mysql','sys','master','model','msdb','tempdb','config','local','admin']
        for item in data['databases']:
            dbname = item['name']
            if sql.where("name=?",(dbname,)).count(): continue
            if not dbname in nameArr:
                if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'MongoDB',self.sid,db_type)): n +=1

        return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),))


    def ResDatabasePassword(self,args):
        """
        @修改用户密码
        """
        id = args['id']
        username = args['name'].strip()
        newpassword = public.trim(args['password'])

        try:
            if not newpassword:
                return public.returnMsg(False, '修改失败，数据库[' + username + ']密码不能为空.');
            if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0:
                return public.returnMsg(False, '数据库密码不能为空或带有特殊字符')

            if re.search('[\u4e00-\u9fa5]',newpassword):
                return public.returnMsg(False,'数据库密码不能为中文，请换个名称!')
        except :
            return public.returnMsg(False, '数据库密码不能为空或带有特殊字符')

        find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find();
        if not find: return public.returnMsg(False, '修改失败，指定数据库不存在.');

        get = public.dict_obj()
        get.sid = find['sid']
        if self.get_local_auth(get):
            db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(find['name'])
            try:
                db_obj.command("updateUser", find['name'], pwd = newpassword)
            except :
                db_obj.command("createUser", find['name'], pwd=newpassword, roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}])

        else:
            return public.returnMsg(False, '修改失败，数据库未开启密码访问.');

        #修改SQLITE
        public.M('databases').where("id=?",(id,)).setField('password',newpassword)

        public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],))
        return public.returnMsg(True,'DATABASE_PASS_SUCCESS',(find['name'],))

    def get_root_pwd(self,args):
        """
        @获取root密码
        """
        config = panelMongoDB().get_options(None)
        sa_path = '{}/data/mongo.root'.format(public.get_panel_path())
        if os.path.exists(sa_path):
            config['msg'] = public.readFile(sa_path)
        else:
            config['msg'] = ''
        config['root'] = config['msg']
        return config



    def get_database_size_by_id(self,args):
        """
        @获取数据库尺寸（批量删除验证）
        @args json/int 数据库id
        """
        total = 0
        db_id = args
        if not isinstance(args,int): db_id = args['db_id']

        find = public.M('databases').where('id=?',db_id).find()
        try:
            db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(find['name'])

            total = tables[0][1]
            if not total: total = 0
        except :
            print(public.get_error_info())

        return total

    def check_del_data(self,args):
        """
        @删除数据库前置检测
        """
        return self.check_base_del_data(args)


    def check_cloud_database_status(self,conn_config):
        """
        @检测远程数据库是否连接
        @conn_config 远程数据库配置，包含host port pwd等信息
        """
        try:
            if not 'db_name' in conn_config: conn_config['db_name'] = None
            sql_obj = panelMongoDB().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])

            db_obj = sql_obj.get_db_obj('admin')
            data = db_obj.command({"listDatabases":1})

            if 'databases' in data:
                return True
            return False
        except Exception as ex:
            return public.returnMsg(False,ex)