#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x3
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 黄文良 <2879625666@qq.com>
# +-------------------------------------------------------------------

#+--------------------------------------------------------------------
#|   自动部署网站
#+--------------------------------------------------------------------

import public,json,os,time;
class obj: id=0;
class deployment_main:
    __setupPath = '/www/server/panel/plugin/deployment';
    __panelPath = '/www/server/panel';
    logPath = '/www/server/panel/plugin/deployment/speed.json'
    timeoutCount = 0;
    oldTime = 0;
    
    #获取列表
    def GetList(self,get):
        self.GetCloudList(get);
        jsonFile = self.__setupPath + '/list.json';
        if not os.path.exists(jsonFile): return public.returnMsg(False,'配置文件不存在!');
        data = {}
        data = json.loads(public.readFile(jsonFile));
            
        if not hasattr(get,'type'): 
            get.type = 0;
        else:
            get.type = int(get.type)
        if not hasattr(get,'search'): 
            search = None
            m = 0
        else:
            search = get.search.encode('utf-8').lower();
            m = 1
            
        tmp = [];
        for d in data:
            i=0;
            if get.type > 0:
                if get.type == d['type']: i+=1
            else:
                i+=1
            if search:
                if d['name'].lower().find(search) != -1: i+=1;
                if d['title'].lower().find(search) != -1: i+=1;
                if get.type > 0 and get.type != d['type']: i -= 1;
            if i>m:tmp.append(d);
            
        data = tmp;
        result = {}
        result['data'] = data;
        jsonFile = self.__setupPath + '/type.json';
        if not os.path.exists(jsonFile): self.GetCloudList(get);
        result['type'] = json.loads(public.readFile(jsonFile));
        return result;
    
    #获取插件列表
    def GetDepList(self,get):
        jsonFile = self.__setupPath + '/list.json';
        if not os.path.exists(jsonFile): return public.returnMsg(False,'配置文件不存在!');
        data = {}
        data = json.loads(public.readFile(jsonFile));
        return data;
        
    
    #从云端获取列表
    def GetCloudList(self,get):
        try:
            import web
            if not hasattr(web.ctx.session,'package'):
                downloadUrl = public.get_url() + '/install/lib/plugin/deployment/package.json';
                tmp = json.loads(public.httpGet(downloadUrl));
                if not tmp: return public.returnMsg(False,'从云端获取失败!');
                jsonFile = self.__setupPath + '/list.json';
                public.writeFile(jsonFile,json.dumps(tmp));
                
                downloadUrl = public.get_url() + '/install/lib/plugin/deployment/type.json';
                tmp = json.loads(public.httpGet(downloadUrl));
                if not tmp: return public.returnMsg(False,'从云端获取失败!');
                jsonFile = self.__setupPath + '/type.json';
                public.writeFile(jsonFile,json.dumps(tmp));
                
                web.ctx.session.package = True
                return public.returnMsg(True,'更新成功!');
            return public.returnMsg(True,'无需更新!');
        except:
            return public.returnMsg(False,'从云端获取失败!');
        
        
    
    #添加程序包
    def AddPackage(self,get):
        jsonFile = self.__setupPath + '/list.json';
        if not os.path.exists(jsonFile): return public.returnMsg(False,'配置文件不存在!');
        
        data = {}
        data = json.loads(public.readFile(jsonFile));
        for d in data:
            if d['name'] == get.dname: return public.returnMsg(False,'您要添加的程序标识已存在!');
            if d['title'] == get.title: return public.returnMsg(False,'您要添加的程序名称已存在!');
        
        if hasattr(get,'rewrite'): get.rewrite = True;
        
        pinfo = {}
        pinfo['name'] = get.dname;
        pinfo['title'] = get.title;
        pinfo['version'] = get.version;
        pinfo['md5'] = get.md5;
        pinfo['rewrite'] = get.rewrite;
        pinfo['php'] = get.php;
        pinfo['ps'] = get.ps;
        pinfo['shell'] = get.shell;
        pinfo['download'] = get.download;
        data.append(pinfo);
        public.writeFile(jsonFile,json.dumps(data));
        return public.returnMsg(True,'添加成功!');
    
    #删除程序包
    def DelPackage(self,get):
        jsonFile = self.__setupPath + '/list.json';
        if not os.path.exists(jsonFile): return public.returnMsg(False,'配置文件不存在!');
        
        data = {}
        data = json.loads(public.readFile(jsonFile));
        
        tmp = [];
        for d in data:
            if d['name'].find(get.dname) != -1: continue;
            tmp.append(d);
        
        data = tmp;
        public.writeFile(jsonFile,json.dumps(data));
        return public.returnMsg(True,'删除成功!');
    
    #下载文件
    def DownloadFile(self,url,filename):
        try:
            import urllib,socket
            socket.setdefaulttimeout(10)
            self.pre = 0;
            self.oldTime = time.time();
            urllib.urlretrieve(url,filename=filename,reporthook= self.DownloadHook)
            self.WriteLogs(json.dumps({'name':'下载文件','total':0,'used':0,'pre':0,'speed':0}));
        except:
            if self.timeoutCount > 5: return;
            self.timeoutCount += 1
            time.sleep(5)
            self.DownloadFile(url,filename)
            
    #下载文件进度回调  
    def DownloadHook(self,count, blockSize, totalSize):
        used = count * blockSize
        pre1 = int((100.0 * used / totalSize))
        if self.pre != pre1:
            dspeed = used / (time.time() - self.oldTime);
            speed = {'name':'下载文件','total':totalSize,'used':used,'pre':self.pre,'speed':dspeed}
            self.WriteLogs(json.dumps(speed))
            self.pre = pre1
    
    #写输出日志
    def WriteLogs(self,logMsg):
        fp = open(self.logPath,'w+');
        fp.write(logMsg)
        fp.close()
    
    #一键安装网站程序
    #param string name 程序名称
    #param string site_name 网站名称
    #param string php_version PHP版本
    def SetupPackage(self,get):
        name = get.dname
        site_name = get.site_name;
        php_version = get.php_version;
        #取基础信息
        find = public.M('sites').where('name=?',(site_name,)).field('id,path').find();
        path = find['path'];
        
        #获取包信息
        pinfo = self.GetPackageInfo(name);
        if not pinfo: return public.returnMsg(False,'指定软件包不存在!');
        
        #检查本地包
        self.WriteLogs(json.dumps({'name':'检查软件包','total':0,'used':0,'pre':0,'speed':0}));
        packageZip = self.__setupPath + '/package/' + name + '.zip';
        isDownload = False;
        if os.path.exists(packageZip):
            md5str = self.GetFileMd5(packageZip);
            if md5str != pinfo['md5']: isDownload = True;
        else:
            isDownload = True;
        
        #下载文件
        
        if isDownload:
            self.WriteLogs(json.dumps({'name':'下载文件','total':0,'used':0,'pre':0,'speed':0}));
            self.DownloadFile(pinfo['download'], packageZip);
        if not os.path.exists(packageZip): return public.returnMsg(False,'文件下载失败!');
        os.system('unzip -o '+packageZip+' -d ' + path + '/');
        
        #设置权限
        self.WriteLogs(json.dumps({'name':'设置权限','total':0,'used':0,'pre':0,'speed':0}));
        os.system('chmod -R 755 ' + path);
        os.system('chown -R www.www ' + path);
        if pinfo['chmod'] != "":
            access = pinfo['chmod'].split(',')
            for chm in access:
                tmp = chm.split('|');
                if len(tmp) != 2: continue;
                os.system('chmod -R ' + tmp[0] + ' ' + path + '/' + tmp[1]);
        
        #安装PHP扩展
        self.WriteLogs(json.dumps({'name':'安装必要的PHP扩展','total':0,'used':0,'pre':0,'speed':0}));
        if pinfo['ext'] != '':
            exts = pinfo['ext'].split(',');
            import files
            mfile = files.files();
            for ext in exts:
                if ext == 'pathinfo': 
                    import config
                    con = config.config();
                    get.version = php_version;
                    get.type = 'on';
                    con.setPathInfo(get);
                else:
                    get.name = ext
                    get.version = php_version
                    get.type = '1';
                    mfile.InstallSoft(get);
        
        
        #执行额外shell进行依赖安装
        self.WriteLogs(json.dumps({'name':'执行额外SHELL','total':0,'used':0,'pre':0,'speed':0}));
        if os.path.exists(path+'/install.sh'): 
            os.system('cd '+path+' && bash ' + 'install.sh');
            os.system('rm -f ' + path+'/install.sh')
            
        #是否执行Composer
        if os.path.exists(path + '/composer.json'):
            self.WriteLogs(json.dumps({'name':'执行Composer','total':0,'used':0,'pre':0,'speed':0}));
            if not os.path.exists(path + '/composer.lock'):
                execPHP = '/www/server/php/' + php_version +'/bin/php';
                if execPHP:
                    if public.get_url().find('125.88'):
                        os.system('cd ' +path+' && '+execPHP+' /usr/bin/composer config repo.packagist composer https://packagist.phpcomposer.com');
                    import panelSite;
                    phpini = '/www/server/php/' + php_version + '/etc/php.ini'
                    phpiniConf = public.readFile(phpini);
                    phpiniConf = phpiniConf.replace('proc_open,proc_get_status,','');
                    public.writeFile(phpini,phpiniConf);
                    os.system('nohup cd '+path+' && '+execPHP+' /usr/bin/composer install -vvv > /tmp/composer.log 2>&1 &');
        
        #写伪静态
        self.WriteLogs(json.dumps({'name':'设置伪静态','total':0,'used':0,'pre':0,'speed':0}));
        swfile = path + '/nginx.rewrite';
        if os.path.exists(swfile):
            rewriteConf = public.readFile(swfile);
            dwfile = self.__panelPath + '/vhost/rewrite/' + site_name + '.conf';
            public.writeFile(dwfile,rewriteConf);
        
        #设置运行目录
        self.WriteLogs(json.dumps({'name':'设置运行目录','total':0,'used':0,'pre':0,'speed':0}));
        if pinfo['run'] != '/':
            import panelSite;
            siteObj = panelSite.panelSite();
            mobj = obj();
            mobj.id = find['id'];
            mobj.runPath = pinfo['run'];
            siteObj.SetSiteRunPath(mobj);
            
        #导入数据
        self.WriteLogs(json.dumps({'name':'导入数据库','total':0,'used':0,'pre':0,'speed':0}));
        if os.path.exists(path+'/import.sql'):
            databaseInfo = public.M('databases').where('pid=?',(find['id'],)).field('username,password').find();
            if databaseInfo:
                os.system('/www/server/mysql/bin/mysql -u' + databaseInfo['username'] + ' -p' + databaseInfo['password'] + ' ' + databaseInfo['username'] + ' < ' + path + '/import.sql');
                os.system('rm -f ' + path + '/import.sql');
                siteConfigFile = path + '/' + pinfo['config'];
                if os.path.exists(siteConfigFile):
                    siteConfig = public.readFile(siteConfigFile)
                    siteConfig = siteConfig.replace('BT_DB_USERNAME',databaseInfo['username'])
                    siteConfig = siteConfig.replace('BT_DB_PASSWORD',databaseInfo['password'])
                    siteConfig = siteConfig.replace('BT_DB_NAME',databaseInfo['username'])
                    public.writeFile(siteConfigFile,siteConfig)
        
        public.serviceReload();
        self.depTotal(name);
        self.WriteLogs(json.dumps({'name':'准备部署','total':0,'used':0,'pre':0,'speed':0}));
        return public.returnMsg(True,pinfo);
    
    #提交安装统计
    def depTotal(self,name):
        try:
            import urllib2
            urllib2.urlopen("https://www.bt.cn/Api/depTotal?name=" + name, timeout = 3)
            return True
        except:
            return False;
    
    #获取进度
    def GetSpeed(self,get):
        try:
            if not os.path.exists(self.logPath): public.returnMsg(False,'当前没有部署任务!');
            return json.loads(public.readFile(self.logPath));
        except:
            return {'name':'准备部署','total':0,'used':0,'pre':0,'speed':0}
     
    #获取包信息
    def GetPackageInfo(self,name):
        data = self.GetDepList(None);
        if not data: return False;
        downUrl = public.get_url() + '/install/package';
        for info in data:
            if info['name'] == name:
                info['download'] = info['download'].replace('{Download}',downUrl);
                return info;
        return False;
    
    #检查指定包是否存在
    def CheckPackageExists(self,name):
        data = self.GetDepList(None);
        if not data: return False;
        for info in data:
            if info['name'] == name: return True;
        
        return False;
    
    #文件的MD5值
    def GetFileMd5(self,filename):
        if not os.path.isfile(filename): return False;
        import hashlib;
        myhash = hashlib.md5()
        f = file(filename,'rb')
        while True:
            b = f.read(8096)
            if not b :
                break
            myhash.update(b)
        f.close()
        return myhash.hexdigest();
    
    #获取站点标识
    def GetSiteId(self,get):
        return public.M('sites').where('name=?',(get.webname,)).getField('id');
    