您的位置:首页 > 数据库

在线批量部署网站代码和数据库版本更新升级

2017-01-11 15:14 579 查看
【项目需求】

从SVN仓库自动checkout指定或全部代码文件,部署更新到指定应用服务器,并保存和比较每台应用服务器上的代码版本和SVN仓库代码版本。数据库实现版本化管理更新。

【项目疑难点】

部署客户端与SVN仓库、应用服务器的Ajax交互的性能、接口问题





【代码举例】

PhingDeploy.class.php   Phing的方式部署类


<?php defined('IN_HEAVEN') or die('Hacking Attempt!');

/**
* PhingDeploy
*
* PHP version 5
*
* @category	4SWeb
* @package     Admin
* @subpackage  Custom
* @author      zhongyiwen
* @version     SVN: $Id: PhingDeploy.class.php 67 2014-11-25 15:12:29Z blin.z $
*/

class PhingDeploy extends DeploySource{
protected $_phingCMD = 'phing';

protected $_buildConf = 'build.configs';
protected $_buildDir = 'phing';
protected $_buildFileMaps = array(
'ftp' => 'ftp_build.xml',
//'svn' => 'svn_build.xml',
'copy' => 'copy_build.xml',
'db' => 'db_build.xml',
'ssh' => 'ssh_build.xml',
'scp' => 'scp_build.xml',
'ssh_query_db_revision' => 'ssh_query_db_revision_build.xml',
'api_query_db_revision' => 'api_query_db_revision_build.xml',
'backup_db' => 'backup_db_build.xml',
'http_request' => 'http_request_build.xml',
'svn' => 'svn_update_build.xml',
);

protected $_runUser = array();
protected $_runTargets = array();
protected $_runEvent = array();

public function __construct($runder=null){

$this->_buildDir = DEPLOY_ROOT_DIR . '/phing';

parent::__construct($runder);

$this->_makeBuildConf();

$phingCMD = DConfig::getConfig('deploy.phing_cmd');
if($phingCMD){
$this->_phingCMD = $phingCMD;
}
}

public function supportedMethods($method=''){
return $method?array_key_exists($method, $this->_buildFileMaps):array_keys($this->_buildFileMaps);
}

public function getBuildConf(){
return $this->_getFullPath($this->_buildConf, $this->_buildDir);
}

public function getBuildFile($method='ftp'){
$file = $this->_buildFileMaps[$method];

if(!$file){
throw new __Exception("Not found build file, method={$method}", SCRIPT_ERR_CONFIG);
}

return $this->_getFullPath($file, $this->_buildDir);
}

protected function _makeBuildConf(){
$confContent = "#=========================================================#";
$confContent .= "\n# Auto Build Configurations for Phing";
$confContent .= "\n# Created on " . date ( 'Y/m/d H:i:s' );
$confContent .= "\n#=========================================================#";

// FTP Configuration
$aFTPAttributes = array('host', 'username', 'password', 'port', 'passive', 'dir', 'level');
if (!empty ( $this->_ftpServerMaps )) {
$confContent .= "\n\n#------------ FTP Server Configuration ----------#";
foreach ( $this->_ftpServerMaps as $server => $settings ) {

$confContent .= "\n\n" . "# {$settings['host']} ftp server";

foreach ( $settings as $k => $v ) {
if (in_array ( $k, $aFTPAttributes )) {
$confContent .= "\n" . "ftp_{$server}.{$k} = {$v}";
}
}
}
}

// SSH Configuration
$aSSHAttributes = array('method', 'host', 'username', 'password', 'port', 'tmpdir', 'level', 'pubkeyfile', 'privkeyfile', 'passphrase');
if (!empty ( $this->_sshServerMaps )) {
$confContent .= "\n\n#------------ SSH Server Configuration ----------#";
foreach ( $this->_sshServerMaps as $server => $settings ) {

$confContent .= "\n\n" . "# {$settings['host']} ssh server";

foreach ( $settings as $k => $v ) {
if (in_array ( $k, $aSSHAttributes )) {
$confContent .= "\n" . "ssh_{$server}.{$k} = {$v}";
}
}
}
}

// Api Configuration
$aApiAttributes = array('url', 'authUser', 'authPassword', 'authScheme', 'responseRegex', 'observerEvents', 'method', 'verbose');
if (!empty ( $this->_apiServerMaps )) {
$confContent .= "\n\n#------------ API Server Configuration ----------#";
foreach ( $this->_apiServerMaps as $server => $settings ) {

$confContent .= "\n\n" . "# {$settings['url']} api server";

foreach ( $settings as $k => $v ) {
if (in_array ( $k, $aApiAttributes )) {
$confContent .= "\n" . "api_{$server}.{$k} = {$v}";
}
}
}
}

// Code Configuration
$aCodeAttributes = array('host', 'username', 'password', 'port', 'passive', 'dir', 'level');
if (!empty ( $this->_targetCodeMaps )) {
$confContent .= "\n\n#------------- Deploy Code Configuration -------------#";
foreach ( $this->_targetCodeMaps as $target => $settings ) {

if(empty($settings['method'])){continue;}

$confContent .= "\n\n" . "# {$settings['host']} {$settings['method']} code target";

foreach ( $settings as $k => $v ) {
if (in_array ( $k, $aCodeAttributes )) {
$confContent .= "\n" . "{$settings['method']}_{$target}.{$k} = {$v}";
}
}
}
}

// Database Configuration
$aDatabaseAttributes = array('host', 'username', 'password', 'port', 'database', 'charset', 'mysql_cmd', 'mysqldump_cmd', 'gzip_cmd', 'backup_dir');
if (!empty ( $this->_targetDatabaseMaps )) {
$confContent .= "\n\n#------------ Deploy Database Configuration ----------#";
foreach ( $this->_targetDatabaseMaps as $target => $settings ) {

$confContent .= "\n\n" . "# {$settings['host']} {$settings['method']} database target";

foreach ( $settings as $k => $v ) {
if (in_array ( $k, $aDatabaseAttributes )) {
$confContent .= "\n" . "db_{$target}.{$k} = {$v}";
}
}
}
}

// Phing Configuration
$confContent .= "\n\n" . "#-------------- Settings for phing --------------#";
// project basedir
$confContent .= "\n" . "build.dir = " . $this->_buildDir;

// save conf
$confFile = $this->getBuildConf();
file_put_contents($confFile, $confContent);
}

protected function _phingCMD($type, $target='', $phingProperties=array()){
$method = '_' . $type . 'CMD';
if(method_exists($this, $method)){
$cmd = $this->$method($target, $phingProperties);
}else{
$cmd = $this->_defaultCMD($type, $target, $phingProperties);
}

$cmd .= " " . DConfig::getConfig('deploy.phing_cmd_args');

return $cmd;
}

protected function _genPhingCMD($buildFile, $properties){
$cmd = $this->_phingCMD . " -f {$buildFile}";

$properties["build.token"] = $this->getToken();

foreach($properties as $p=>$v){
if(is_array($v) || is_object($v)){
$v = serialize($v);
}
$cmd .= " -D{$p}=\"" . addcslashes($v, '"'). "\"";
}

return $cmd;
}

protected function _codeCMD($target, $properties=array()) {
if (! empty ( $this->_targetCodeMaps [$target] ['method'] )) {
$method = $this->_targetCodeMaps [$target] ['method'];
$server = $target;

} else if (! empty ( $this->_targetCodeMaps [$target] ['auth'] )) {
$targetAuth = $this->_targetCodeMaps [$target] ['auth'];
$method = substr ( $targetAuth, 0, strpos ( $targetAuth, '.' ) );

$server = substr ( $targetAuth, strpos ( $targetAuth, '.' ) + 1 );
} else {
$this->_setErrorMsg ( "Invalid Config for target: $target" );
return false;
}

$buildFile = $this->getBuildFile ( $method );

if (! $buildFile ) {
$this->_setErrorMsg ( "Couldn't found build file for method: $method" );
return false;
}

$sourceDir = $this->getWorkingDir ('code');
$toDir = $this->_targetCodeMaps [$target]['dir'];

$defaultProperties = array();

4000

switch($method){
case 'ftp':
$defaultProperties["ftpserver"] = "ftp_{$server}";
$defaultProperties['ftp.sourcedir'] = $sourceDir;
$defaultProperties['ftp.toDir'] = $toDir;
break;
case 'scp':
$defaultProperties["sshserver"] = "ssh_{$server}";
$defaultProperties['scp.sourceDir'] = $sourceDir;
$defaultProperties['scp.toDir'] = $toDir;
$defaultProperties['scp.autocreate'] = 'true';
break;
case 'copy':
$defaultProperties['copy.sourceDir'] = $sourceDir;
$defaultProperties['copy.toDir'] = $toDir;
break;
default:
throw new __Exception("Unknown method: $method", SCRIPT_ERR_CONFIG);
}

return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties));
}

protected function _databaseCMD($target, $properties=array()) {
$method = 'db';
if (! $this->supportedMethods ( $method )) {
$this->_setErrorMsg ( "$method is not supported by phing for target $target" );
return false;
}

$buildFile = $this->getBuildFile ( $method );
$serverProperty = $method . "_" . $target;

$workingDir = $this->getWorkingDir();

$dbDeltaDir = $this->getWorkingDir('deltas');
$dbScriptDir = DEPLOY_RUN_DIR . '/scripts';
$dbDeltaSet = DConfig::getConfig(array('deploy.target_database_maps', $target, 'database'));
$dbTableName = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table'));

$dbDeltaSet = 'Main';

$defaultProperties = array();
$defaultProperties["dbserver"] = $serverProperty;
$defaultProperties["db.delta_dir"] = $dbDeltaDir;
$defaultProperties["db.script_dir"] =$dbScriptDir;
$defaultProperties["db.delta_set"] = $dbDeltaSet;
$defaultProperties["db.delta_revision"] = '999';
$defaultProperties["db.changelog_table"] = $dbTableName?$dbTableName:'changelog';

$defaultProperties["build.token"] = $this->getToken();

$targetAuth = $this->_targetDatabaseMaps[$target]['auth'];
$authType = '';
if($targetAuth){
$authType = substr($targetAuth, 0, strpos($targetAuth, '.'));
}

if($authType=='ssh'){
$sshServer = substr($targetAuth, 3+1);

$scp2Dir = DConfig::getConfig(array('deploy.ssh_server_maps', $sshServer, 'tmpdir'));

if(!$scp2Dir){
throw new __Exception("Couldn't found ssh server tmpdir", SCRIPT_ERR_CONFIG);
}

$defaultProperties['sshserver'] = "ssh_{$sshServer}";
$defaultProperties['scp.toDir'] = $scp2Dir;
$defaultProperties['db.change_logs'] = $workingDir . "/" . $target . "_db_revision.xml";;
}elseif($authType=='api'){
$apiServer = substr($targetAuth, 3+1);

$defaultProperties['apiserver'] = "api_{$apiServer}";
$defaultProperties['db.target_db'] = $this->_targetDatabaseMaps[$target]['auth.options']['getparameter']['db'];
$defaultProperties['db.change_logs'] = $workingDir . "/" . $target . "_db_revision.json";;
}

return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties));
}

protected function _defaultCMD($type, $target='', $properties=array()) {
$buildFile = $this->getBuildFile ( $type );
$defaultProperties = array();

return $this->_genPhingCMD($buildFile, array_merge($defaultProperties, $properties));
}

public function callPhing($cmd, $target, $properties=array()){
return $this->_callPhing($cmd, $target, $properties);
}

protected function _callPhing($type='code', $target='', $properties=array()){
/*
if(!$target){
$this->_setErrorMsg( "Target can not be empty" );
return DeploySource::STATUS_STOP_ERROR;
}*/

$logFile = $this->getLogFile($type);

if(!isset($properties['build.logfile'])){
$properties['build.logfile'] = $logFile;
}

$cmd = $this->_phingCMD($type, $target, $properties);
if(!$cmd){
return DeploySource::STATUS_STOP_ERROR;
}

// $cmd .= " -verbose";

if ($this->_enableLog && $logFile) {
// $cmd .= " -logfile {$logFile}";

// use pipe to write log & errors
$cmd .= " 1>>$logFile 2>&1";
}

//die($cmd);

$this->writeLog("\n\nSTART DEPLOY TARGET $target");
$this->writeLog("\nPhing CMD: $cmd \n");

// Note: must close log file resource before running phing
$this->writeLog("", "", false);

// escape command
$cmd = escapeshellcmd($cmd);

$cmdOutput = '';
$cmdStatus = '';

// execute command
$cmdResult = exec($cmd, $cmdOutput, $cmdStatus);

//echo "<br />CMD Result=" . print_r($cmdResult, true);
//echo "<br />CMD Output=" . print_r($cmdOutput, true);
//echo "<br />CMD Status=" . print_r($cmdStatus, true);

// exit code returned by Phing is not credible,
// let's check error ourself
$aResultLines = tailFile($logFile, 20, true);

//$this->writeLog("\ncmd Result: " . print_r($cmdResult, true));
//$this->writeLog("\nresult Lines: : " . print_r($aResultLines, true));

// print_r($aResultLines);
/*
Examples:
Array
(
[0] =>

[1] => BUILD FINISHED

[2] =>

[3] => Total time: 3.2202 seconds

[4] =>

)
Array
(
[0] =>

[1] => BUILD FAILED

[2] => E:\w3root\dev\websvn\deploy\phing\ftp_build.xml:9:33: Could not connect to FTP server ftp2.feet.com on port 21: Connection to host failed

[3] => Total time: 0.3054 seconds

[4] =>

)

Array
(
[0] => BUILD FAILED

[1] => E:\w3root\dev\websvn\deploy\phing\db_build.xml:35:12: E:\w3root\dev\websvn\deploy\phing\db_build.xml:53:20: E:\w3root\dev\websvn\deploy\phing\db_build.xml:62:34: Failed to upgrade to db revision: 3, Please check your delta do sql syntax! Mysql error message: ERROR 1050 (42S01) at line 6: Table 'export_setting_reg' already exists

[2] =>

[3] => Total time: 5.6628 seconds

[4] =>

)
*/

//$buildCode = trim($aResultLines[0]);

$isBuildSucc = true;
$buildReason = '';
foreach($aResultLines as $i=>$line){
$line = trim($line);
if($line && !strcasecmp($line, 'BUILD FAILED')){
$isBuildSucc = false;
$buildReason = $aResultLines[$i+1];
}elseif($line && !strcasecmp($line, 'BUILD FINISHED')){
$isBuildSucc = true;
$buildReason = '';
}
}

// succ: BUILD FINISHED
// fail: BUILD FAILED

// E:\w3root\dev\websvn\deploy\phing\ftp_build.xml:9:28: Could not login to FTP server ftp.feet.com on port 21 with username www2: Unable to login
if(!$isBuildSucc){
$this->_setErrorMsg("[Target $target] Phing Error: $buildReason");
$this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $buildReason);
$this->writeLog("\n\nFailed deploy, Phing Error occurred: $buildReason");
return DeploySource::STATUS_STOP_ERROR;
}

// check phing exit code
/*

Exitcode	Description
-2	Environment not properly defined
-1	Parameter error occured, printed help screen
0	Successful execution, no warnings, no errors
1	Successful execution, but warnings occured

*/
switch($cmdStatus){
case 0:
$this->writeLog("\nSuccessful execute deploy by Phing, no errors, nor warnings.");
$this->writeLog("\n\nEND DEPLOY TARGET $target");
return DeploySource::STATUS_DONE_SUCCESS;
case 1:
$this->writeLog("\nSuccessful execute deploy by Phing, but warnings occured.");
$this->writeLog("\n\nEND DEPLOY TARGET $target");
return DeploySource::STATUS_DONE_WARNING;
case -1:
$statusText = "Parameter error occured, printed help screen";
$this->_setErrorMsg("[Target $target] Phing Error: $cmdStatus $statusText");
$this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $statusText);
$this->writeLog("\n\nFailed deploy files, Phing Error occurred: $cmdStatus $statusText");
return DeploySource::STATUS_STOP_ERROR;
case -2:
$statusText = "Environment not properly defined";
$this->_setErrorMsg("[Target $target] Phing Error: $cmdStatus $statusText");
$this->_setErrorTarget($target, "部署失敗", DeploySource::STEP_PHING_DEPLOY, $statusText);
$this->writeLog("\n\nFailed deploy files, Phing Error occurred: $cmdStatus $statusText");

return DeploySource::STATUS_STOP_ERROR;
default:
throw new __Exception("Unknown phing exit code: {$cmdStatus}", SCRIPT_ERR_CONFIG);
}
}

protected function _verifyFiles($type, $target){
$logFile = $this->getLogFile($type);

$this->writeLog("\n\nTry to verify files of target $target");

$totalTargets = count($this->_runSettings['targets']);
$currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1;

$this->writeProgress("開始校驗是否全部文件部署到 {$currentTargetSeq}/{$totalTargets}: {$target}目标服務器", DeploySource::STEP_VERIFY_DEPLOY);

if(empty($this->_runSettings['parsedLogs'])){
$errorMsg = "Verify Parsed Logs is empty";
$this->_setErrorMsg($errorMsg);
$this->writeLog("\nSKIP VERIFY, " . $errorMsg);

return DeploySource::STATUS_STOP_ERROR;
}

$startTag = "\n\nSTART DEPLOY TARGET $target";
$endTag = "\n\nEND DEPLOY TARGET $target";

$logContent = file_get_contents($logFile);
if (($startPos = strrpos ( $logContent, $startTag )) === false) {
$errorMsg = "Couldn't found verify start position: $startTag";
$this->_setErrorMsg ( $errorMsg );
$this->writeLog ( "\nSKIP VERIFY, " . $errorMsg );
return DeploySource::STATUS_STOP_ERROR;
} else if (($endPos = strrpos ( $logContent, $endTag )) === false) {
$errorMsg = "Couldn't found verify end position: $endTag";
$this->_setErrorMsg ( $errorMsg );
$this->writeLog ( "\nSKIP VERIFY, " . $errorMsg );

return DeploySource::STATUS_STOP_ERROR;
}

$this->writeLog("\nFound verify position [{$startPos} ~ {$endPos}]");

$length = $endPos - $startPos + strlen ( $endTag );
$tagContent = substr ( $logContent, $startPos, $length );
$aLogFiles = !empty($this->_runCodeRevisions)?$this->_runCodeRevisions:$this->_runSettings['parsedLogs'];
$total = count($this->_runSettings['parsedLogs']);

if (! empty ( $this->_targetCodeMaps [$target] ['method'] )) {
$method = $this->_targetCodeMaps [$target] ['method'];
} else if (! empty ( $this->_targetCodeMaps [$target] ['auth'] )) {
$targetAuth = $this->_targetCodeMaps [$target] ['auth'];
$method = substr ( $targetAuth, 0, strpos ( $targetAuth, '.' ) );
}

$method = strtolower($method);
switch($method){
case 'ftp':
$aFailFiles = $this->_verify_ftp_log($target, $aLogFiles, $tagContent);
break;
case 'copy':
$aFailFiles = $this->_verify_copy_log($target, $aLogFiles, $tagContent);
break;
default:
$errorMsg = "Unknown deploy method: {$method}";
$this->_setErrorMsg($errorMsg);
$this->writeLog("\nSKIP VERIFY, " . $errorMsg);
return DeploySource::STATUS_STOP_ERROR;
}

$fail = $aFailFiles===true?0:count($aFailFiles);

$this->writeLog ( "\nVerify Finished, total checked $total files, failed: {$fail} files." );

if ($aFailFiles!==true) {
if (is_array ( $aFailFiles )) {
foreach ( $aFailFiles as $file ) {
$aUpdate = array ();
$aUpdate ['file'] = $file;
$aUpdate ['step'] = self::STEP_VERIFY_DEPLOY;
$aUpdate ['target'] = $target;
$aUpdate ['status'] = "Verify Error:文件未部署成功";
$aUpdate ['remark'] = "請重新部署該文件";
$aUpdate ['revision'] = $this->_runSettings ['revision'];

$this->_setErrorFile ( $aUpdate );
}
}
$this->_setErrorMsg ( "有{$fail}個文件部署到{$target}未成功,請重新部署" );
return DeploySource::STATUS_DONE_WARNING;
} else {
return DeploySource::STATUS_DONE_SUCCESS;
}
}

protected function _verify_ftp_log($target, $aLogFiles, $tagContent){
$aFailFiles = array();
foreach ( $aLogFiles as $log ) {
$revision = $this->_runSettings ['revision'];

if (is_array ( $log )) {
if (! empty ( $log ['dumpFile'] )) {
$matchLine = "[ftpdeploy] Will copy {$log['dumpFile']} to {$log['file']}";
} else {
$matchLine = " to {$log['file']}";
}

$file = $log ['file'];

if (! empty ( $log ['revision'] )) {
$revision = $log ['revision'];
}
} else {
$file = $log;
$matchLine = " to $file";
}

if (($linePos = strstr ( $tagContent, $matchLine )) === false) {
$aFailFiles[] = $file;
$this->writeLog ( "\n[{$matchLine}] matched fail, please deploy this file again" );
} else {
$this->writeLog ( "\n[{$matchLine}] matched success" );
}
}

return $aFailFiles?$aFailFiles:true;
}

protected function _verify_copy_log($target, $aLogFiles, $tagContent){
$total = count($aLogFiles);
$matchLine = "[copy] Copying {$total} files to";

if (($linePos = strstr ( $tagContent, $matchLine )) === false) {
return false;
} else {
return true;
}
}

protected function _phingDeploy($type){
switch($type){
case 'code':
return $this->_deployCode();
break;
case 'database':
return $this->_deployDatabase();
break;
case 'svn':
return $this->_deploySVN();
break;
default :
throw new __Exception("Unknown deploy type: $type" , SCRIPT_ERR_CONFIG);
}
}

protected function _deployCode(){

$runOK = false;

if(!$this->_runSettings['parsedLogs']){
$this->_runSettings['parsedLogs'] = $this->parseUpdateLogs($this->_runSettings['updateLogs']);
}

$totalFiles = count($this->_runSettings['parsedLogs']);

$this->_runSettings['deploySteps'] = array(DeploySource::STEP_SVN_COPY, DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_PHING_DEPLOY, DeploySource::STEP_VERIFY_DEPLOY);

$returnStatus = DeploySource::STATUS_DONE_SUCCESS;

$oEvent = new EventModel();
$oTarget = new TargetModel();

foreach ( $this->_runSettings ['deploySteps'] as $step ) {

switch ($step) {
case DeploySource::STEP_SVN_COPY :
$this->writeProgress("開始去SVN倉庫獲取文件", self::STEP_SVN_COPY);
$runOK = $this->dumpCodeFromSVN ( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] );
break;

case DeploySource::STEP_CHECK_REVISION :
$this->writeProgress("開始去SVN倉庫檢查文件版本", DeploySource::STEP_CHECK_REVISION);
$runOK = $this->checkRevisionFromSVN( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] );
break;

case DeploySource::STEP_PHING_DEPLOY :
$totalTargets = count($this->_runSettings['targets']);

foreach ( $this->_runSettings ['targets'] as $target ) {
$currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1;
$this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY);

$startTime = time();
$oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser, $totalFiles);

$runOK = $this->_callPhing ( 'code', $target );

$endTime = time();
$elapsedSecs = $endTime - $startTime;
$succFiles = 0;

$isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true;

// IGNORE phing stop error
if (!$isSucc) {
$this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY);
$returnStatus = DeploySource::STATUS_DONE_WARNING;
}else{
// deploy succ or warning
$this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY);

$this->writeProgress("保存目标 {$target}代碼文件版本。。。", self::STEP_PHING_DEPLOY);

$succFiles = $this->_saveTargetCodeRevisions($target);
}

// save deploy target status
$oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, $succFiles, $this->getErrorMsg(" "));

$oTarget->lastDeploy($this-
1bb8c
>_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser);
}

break;
case DeploySource::STEP_VERIFY_DEPLOY :
foreach ( $this->_runSettings ['targets'] as $target ) {
$runOK = $this->_verifyFiles ('code', $target );

// IGNORE verify stop error
if($runOK==DeploySource::STATUS_DONE_WARNING){
$returnStatus = DeploySource::STATUS_DONE_WARNING;
}
}
break;
default :
throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG );
}

if($runOK==DeploySource::STATUS_DONE_WARNING){
$returnStatus = DeploySource::STATUS_DONE_WARNING;
}
else if ($runOK == DeploySource::STATUS_STOP_ERROR) {
$returnStatus = DeploySource::STATUS_STOP_ERROR;
break;
}
}

return $returnStatus;
}

protected function _deployDatabase(){

$runOK = false;

if($this->_runSettings['backup']){
$this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION,  DeploySource::STEP_SVN_COPY, DeploySource::STEP_BACKUP_BEFORE, DeploySource::STEP_PHING_DEPLOY);
}else{
$this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION,  DeploySource::STEP_SVN_COPY, DeploySource::STEP_PHING_DEPLOY);
}

$oEvent = new EventModel();
$oTarget = new TargetModel();

foreach ( $this->_runSettings ['deploySteps'] as $step ) {

switch ($step) {
case DeploySource::STEP_SVN_COPY :
$saveDeltaDir = $this->getWorkingDir('deltas');

$this->writeLog("\n\nSTART dump dir from SVN repository " .  $this->_runSettings ['repname'] . ", dir " . $this->_runSettings ['path']);
$this->writeProgress("開始去SVN倉庫獲取文件", self::STEP_SVN_COPY);

$dumpDeltaStatus = $this->dumpDirFromSVN ( $this->_runSettings ['repname'], $this->_runSettings ['path'], $this->_runSettings ['revision'] , $saveDeltaDir);
if($dumpDeltaStatus){
$runOK = DeploySource::STATUS_DONE_SUCCESS;
}else{
$runOK = DeploySource::STATUS_STOP_ERROR;
}

break;

case DeploySource::STEP_CHECK_REVISION:
$aTargetRevisions = $this->checkDBRevision ( $this->_runSettings );
if($aTargetRevisions){
$runOK = DeploySource::STATUS_DONE_SUCCESS;
}else{
$runOK = DeploySource::STATUS_STOP_ERROR;
}
break;

case DeploySource::STEP_BACKUP_BEFORE:
$runOK = $this->backupDB($this->_runSettings);
$isSucc = $runOK == DeploySource::STATUS_STOP_ERROR ? false : true;

if (! $isSucc) {
$this->writeProgress ( "備份發生錯誤:" . $this->getErrorMsg ( " " ), self::STEP_PHING_DEPLOY );
$returnStatus = DeploySource::STATUS_DONE_WARNING;
} else {
// deploy succ or warning
$this->writeProgress ( "成功完成備份!", self::STEP_PHING_DEPLOY );
}
break;

case DeploySource::STEP_PHING_DEPLOY :
$totalTargets = count($this->_runSettings['targets']);

foreach ( $this->_runSettings ['targets'] as $target ) {
$currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1;

$this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY);

$currentDB = $this->_targetDatabaseMaps[$target]['database'];
$currentDBRevision =  $this->_runSettings['targetRevisions'][$target]['revision'];
$upgradeDBRevision = $this->_runSettings['upgrade2Revision'];

$dbScriptDir = DEPLOY_RUN_DIR . '/scripts';
$doSQLFile = 'deploy-' . $this->_runSettings['token'] . '-' . $currentDB . '.sql';
$undoSQLFile = 'undo-' . $this->_runSettings['token'] . '-' . $currentDB . '.sql';

$properties = array();
$properties['db.delta_revision'] = $upgradeDBRevision;
$properties['db.script_dir'] =$dbScriptDir;
$properties['build.deploy_file_name'] = $doSQLFile;
$properties['build.deploy_undofile_name'] = $undoSQLFile;

// force roll back
$properties['build.roll_back'] = $this->_runSettings['rollback'];

$this->writeLog("\nCurrent Target {$currentDB} DB Revision is {$currentDBRevision}, will upgrade to new revision {$upgradeDBRevision}.");

$this->writeLog("\ndo SQL File: " . $dbScriptDir . '/' . $doSQLFile);
$this->writeLog("\nundo SQL File: " . $dbScriptDir . '/' . $undoSQLFile);

$targetAuth = $this->_targetDatabaseMaps[$target]['auth'];
if($targetAuth){
$dotPos = strpos($targetAuth, '.');
$authType = substr($targetAuth, 0, $dotPos);

switch ($authType){
case 'ssh':
$authServer = substr($targetAuth, $dotPos+1);
$properties['sshserver'] = "ssh_{$authServer}";

$this->writeLog("\nrun deploy process through " . $authType . " server: " . $authServer);

break;
case 'api':
$authServer = substr($targetAuth, $dotPos+1);
$properties['apiserver'] = "api_{$authServer}";

$this->writeLog("\nrun deploy process through " . $authType . " server: " . $authServer);
break;
default:
throw new __Exception("Unknown auth type: $authType", SCRIPT_ERR_CONFIG);
}
}

$startTime = time();
$oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser);

$runOK = $this->_callPhing ( 'database', $target, $properties );

$endTime = time();
$elapsedSecs = $endTime - $startTime;
$succFiles = 0;

$isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true;

$doSQL = file_get_contents($dbScriptDir . '/' . $doSQLFile);
$undoSQL = file_get_contents($dbScriptDir . '/' . $undoSQLFile);

$this->writeLog("\ndo SQL: \n" . $doSQL);
$this->writeLog("\nundo SQL: \n" . $undoSQL);

if (!$isSucc) {
$this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY);
$returnStatus = DeploySource::STATUS_DONE_WARNING;
}else{
// deploy succ or warning
$this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY);

$this->writeProgress("保存目标 {$target} 數據庫版本。。。", self::STEP_PHING_DEPLOY);

$succFiles = $this->_saveTargetDatabaseRevisions($target, $currentDB, $upgradeDBRevision, $doSQL, $undoSQL);
}

// save deploy target status
$oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, NULL, $this->getErrorMsg(" "));

$oTarget->lastDeploy($this->_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser);
}
break;

default :
throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG );
}

if ($runOK == DeploySource::STATUS_STOP_ERROR) {
break;
}
}

return $runOK;
}

protected function _deploySVN(){

$runOK = false;

$this->_runSettings['deploySteps'] = array(DeploySource::STEP_CHECK_REVISION, DeploySource::STEP_PHING_DEPLOY);

$oEvent = new EventModel();
$oTarget = new TargetModel();

$logFile = $this->getLogFile($this->_runSettings['type']);

foreach ( $this->_runSettings ['deploySteps'] as $step ) {

switch ($step) {
case DeploySource::STEP_CHECK_REVISION:
$aTargetRevisions = $this->checkSVNRevision ( $this->_runSettings );
if($aTargetRevisions){
$runOK = DeploySource::STATUS_DONE_SUCCESS;
}else{
$runOK = DeploySource::STATUS_STOP_ERROR;
}
break;

case DeploySource::STEP_PHING_DEPLOY :
$totalTargets = count($this->_runSettings['targets']);

foreach ( $this->_runSettings ['targets'] as $target ) {
$currentTargetSeq = array_search($target, $this->_runSettings['targets'])+1;

$this->writeProgress("开始部署到第 {$currentTargetSeq}/{$totalTargets}: {$target} 目标", self::STEP_PHING_DEPLOY);

$currentSVN = $this->_targetSVNMaps[$target]['svn'];
$currentSVNRevision =  $this->_runSettings['targetRevisions'][$target]['revision'];
$newRevision = $upgradeSVNRevision = $this->_runSettings['upgrade2Revision'];

$this->writeLog("\nCurrent Target {$currentSVN} SVN Revision is {$currentSVNRevision}, will upgrade to new revision {$upgradeSVNRevision}.\n");

$targetAuth = $this->_targetSVNMaps[$target]['auth'];
$targetRepository = $this->_targetSVNMaps[$target]['repository'];
//$targetBranch = $this->_targetSVNMaps[$target]['branch'];
$targetPath = $this->_targetSVNMaps[$target]['path'];

if($targetAuth){
$dotPos = strpos($targetAuth, '.');
$authType = substr($targetAuth, 0, $dotPos);
}else{
$authType = 'local';
}

$properties = array();
$properties['build.token'] = $this->_runSettings['token'];

$properties['svn.target'] = $currentSVN;
$properties['svn.current_revision'] = $currentSVNRevision;
$properties['svn.upgrade_revision'] = $upgradeSVNRevision;

switch ($authType) {
case 'local':
/*
global $config;
$rep_conf = $config->findRepository($targetRepository);

//$properties['svn.svnpath'] = $rep_conf->path . ($targetBranch?((($t=substr($rep_conf->path, -1))!='/' && $t!='\\')?'/':'') . $targetBranch : '');
$properties['svn.svnpath'] = $config->getSvnCommand();
$properties['svn.username'] = $rep_conf->username;
$properties['svn.password'] = $rep_conf->password;
$properties['svn.nocache'] = 'true';
$properties['svn.todir'] = $targetPath;
$properties['svn.revision'] = $upgradeSVNRevision;
$properties['svn.trustServerCert'] = $config->_svnTrustServerCert?'true':'false';
*/

$startTag = "---START SVN UPDATE---";
$endTag   = "----END SVN UPDATE----";

$this->writeLog($startTag."\n");

$this->writeLog("Check repository existence");

global $config;
$cmdString = $config->getSvnCommand() . ' info "' . $targetPath . '" --xml 2>&1';
exec($cmdString, $cmdOutput, $cmdStatus);
$cmdOutput = implode("", $cmdOutput);
$cmdOutput = trim($cmdOutput);

if(($pos=stripos($cmdOutput, "<?xml"))){
$error = substr($cmdOutput, 0, $pos);
$notcopy = "is not a working copy";
if (stripos ( $error, $notcopy )) {
// Checkout it
$status = svncheckout($targetRepository, $targetPath, $upgradeSVNRevision, $this->_runSettings['branch'], $logFile);
} else {
$status = 0;
$this->_setErrorMsg($error);
}
}else{
//$status = svnupdate($targetRepository, $targetPath, $upgradeSVNRevision, $logFile);

$status = svnswitch($targetRepository, $targetPath, $upgradeSVNRevision, $this->_runSettings['branch'], $logFile);
}

$this->writeLog($endTag);

$runOK = $status==0?DeploySource::STATUS_DONE_SUCCESS:DeploySource::STATUS_STOP_ERROR;

$logContent = file_get_contents_utf8($logFile);
$startPos = strrpos($logContent, $startTag);
$endPos = strrpos($logContent, $endTag);

if($startPos && $endPos && $endPos>$startPos){
$updateLog = substr($logContent, $startPos+strlen($startTag)+1, $endPos-$startPos-strlen($endTag)-strlen(PHP_EOL));

//debugLog($updateLog);

// At revision 1095.
// Updated to revision 1095.
$pattern1 = '/At\srevision\s([0-9]+)\./';
$pattern2 = '/Updated\sto\srevision\s([0-9]+)\./';
$pattern3 = '/Checked\sout\srevision\s([0-9]+)\./';

if(preg_match($pattern1, $updateLog, $matches) || preg_match($pattern2, $updateLog, $matches) || preg_match($pattern2, $updateLog, $matches)){
//debugLog($maches);
$newRevision = $matches[1];
}
}

break;
case 'api' :
$authServer = substr ( $targetAuth, $dotPos + 1 );
$properties ['apiserver'] = "api_{$authServer}";

$this->writeLog ( "\nrun deploy process through " . $authType . " server: " . $authServer );
break;
default :
throw new __Exception ( "Unknown auth type: $authType", SCRIPT_ERR_CONFIG );
}

$startTime = time();
$oEvent->startDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $this->_runUser);

//$runOK = $this->_callPhing ( 'svn', $target, $properties );

$endTime = time();
$elapsedSecs = $endTime - $startTime;
$succFiles = 0;

$isSucc = $runOK == DeploySource::STATUS_STOP_ERROR?false:true;

if (!$isSucc) {
$this->writeProgress("部署到目标 {$target} 發生錯誤:" . $this->getErrorMsg(" "), self::STEP_PHING_DEPLOY);
$returnStatus = DeploySource::STATUS_DONE_WARNING;
}else{
// deploy succ or warning
$this->writeProgress("成功部署到目标 {$target}", self::STEP_PHING_DEPLOY);

if(!strcasecmp('HEAD', $newRevision)){
$newRevision = 0;
}

$this->writeProgress("保存目标 {$target} SVN代码版本。。。", self::STEP_PHING_DEPLOY);
$this->_saveTargetSVNRevisions($target, $currentSVN, $newRevision, $updateLog);
}

// save deploy target status
$oEvent->endDeployTarget($this->_runEvent['eventId'], $this->_runTargets[$target]['targetId'], $isSucc, $elapsedSecs, NULL, $this->getErrorMsg(" "));

$oTarget->lastDeploy($this->_runTargets[$target]['targetId'], $this->_runEvent, $this->_runUser);
}
break;

default :
throw new __Exception ( "Unknown deploying step $step", SCRIPT_ERR_CONFIG );
}

if ($runOK == DeploySource::STATUS_STOP_ERROR) {
break;
}
}

return $runOK;
}

public function runDeploy($aSettings){

$this->_runSettings($aSettings);

$executor = $this->getExecutor();

$oEvent = new EventModel();
$oUser = new UserModel();

$serverHints = implode(" ", DeploySource::getTargetHints($this->_runSettings['targets'], $this->_runSettings['type'], true));

$endTime = $startTime = time();
$costTime = 0;

// clear errors before deploy
$this->clearErrors();

$this->writeLog("\n\nSTART DEPLOY");
$this->writeLog("\n[ $executor ] Try to deploy files to $serverHints on " . date('Y-m-d H:i:s', $startTime));

$this->writeLog("\nSource code is: repname=" . $this->_runSettings['repname'] . " branch=" . $this->_runSettings['branch'] . " revision=" . $this->_runSettings['revision']);

// give me run more time
if($this->_runSettings['maxExecuteTime']){
set_time_limit($this->_runSettings['maxExecuteTime']);
$this->writeLog("\nMax execute time "  . $this->_runSettings['maxExecuteTime'] . " seconds" );
}

// 清空工作目錄
//if($this->_runSettings['type']=='code')
$this->cleanWorkingDir();

$oEvent->startDeploy($this->_runEvent['eventId'], $this->_runUser, 0);

$runOK = $this->_phingDeploy($this->_runSettings['type']);

$endTime = time();
$costTime = $endTime - $startTime;
$readableCostTime = time2readable($costTime);
$this->writeLog("\n\nElapsed time: $costTime seconds");

switch ($runOK) {
case DeploySource::STATUS_STOP_ERROR :
$this->writeLog ( "\n\nSTOP DEPLOY, Error Occurred!" );
$this->writeProgress ( "發生錯誤,停止部署" );
$isSucc = false;
break;
case DeploySource::STATUS_DONE_WARNING:
$this->writeLog ( "\n\nDEPLOY DONE, But Warnings Occurred!" );
$this->writeProgress ( "完成部署,有警告錯誤發生!" );
$isSucc = true;
break;
case DeploySource::STATUS_DONE_SUCCESS:
$this->writeLog ( "\n\nSUCC DEPLOY!" );
$this->writeProgress ( "成功完成部署!耗時 $readableCostTime" );
$isSucc = true;
break;
default:
throw new __Exception ( "Unknown deploying status $runOK", SCRIPT_ERR_CONFIG );
}

// 部署结束
//$totalFiles = count($this->_runSettings ['parsedLogs']);
$succFiles = $totalFiles = count($this->_runCodeRevisions);
$oEvent->endDeploy($this->_runEvent['eventId'], $isSucc, $costTime, $succFiles, $this->getErrorMsg(" "), $totalFiles);

// 记录部署日志

// 记录用户最后部署
$oUser->lastDeploy($this->_runUser['uid'], $this->_runEvent);

// 清空工作目錄
if($this->_runSettings['type']=='code')  $this->cleanWorkingDir ();

return $runOK;
}

public function checkCodeRevision($aSettings){
$aR = array();

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$this->writeLog("\n\nSTART check Code Revision");

$this->writeProgress("檢查代碼版本", DeploySource::STEP_CHECK_REVISION);

if(!$this->_runSettings['parsedLogs']){

$this->writeProgress("分析代碼文件", DeploySource::STEP_CHECK_REVISION);

$this->_runSettings['parsedLogs'] = $this->parseUpdateLogs($this->_runSettings['updateLogs']);

$this->writeProgress("獲取SVN倉庫文件版本", DeploySource::STEP_CHECK_REVISION);
$runOK = $this->checkRevisionFromSVN( $this->_runSettings ['parsedLogs'], $this->_runSettings ['repname'], $this->_runSettings ['branch'], $this->_runSettings ['revision'] );

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return false;
}

}else{
$this->_errorMsgs[] = "log files is empty";
return false;
}

$totalTargets = count($this->_runSettings['targets']);

// Target Database Revision
foreach($aSettings['targets'] as $target){

$currentTargetSeq = array_search($target, $aSettings['targets'])+1;
$this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: {$target} 代碼版本", DeploySource::STEP_CHECK_REVISION);

$aRevision = $this->_checkTargetCodeRevision($target);
$aR['targetRevisions'][$target] = $aRevision;

// 同時保存到$this->_runSettings中
$this->_runSettings['targetRevisions'][$target] = $aRevision;
}

//$this->writeLog(print_r($this->_runSettings['targetRevisions'], true), 'debug');

return $aR;
}

protected function _checkTargetCodeRevision($target){
static $oRevision = null, $aFiles = array();

if(!$oRevision){
$oRevision = new CodeRevisionModel();
}

if(!$aFiles){
$aFiles = array_keys($this->_runCodeRevisions);
}

$aReturn = array();

$aTargetRevisions = $oRevision->getFileRevisions($aFiles, $target);

//$this->writeLog(print_r($aTargetRevisions, true), 'debug');

foreach($this->_runCodeRevisions as $file => $svnRev){
$aReturn[$file] = array(
'update' => array('revision' => $svnRev['revision'], 'author' => $svnRev['author'], 'date' => $svnRev['date']),
'target' => isset($aTargetRevisions[$file][$target])
?array('revision' => $aTargetRevisions[$file][$target]['revision'], 'author' => $aTargetRevisions[$file][$target]['revAuthor'], 'date' => date('Y-m-d H:i:s', $aTargetRevisions[$file][$target]['revTime']))
:array(),
);

$sRev = $aReturn[$file]['update']['revision'];
$tRev = $aReturn[$file]['target']['revision'];
if($sRev && $tRev){
$aReturn[$file]['compare'] = $sRev>$tRev?'older':($sRev<$tRev?'newer':'update');
}else{
$aReturn[$file]['compare'] = 'unknown';
}
$aReturn[$file]['differ'] = $sRev - $tRev;
}

return $aReturn;
}

public function checkDBRevision($aSettings){
$aR = array();

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$this->writeLog("\n\nSTART check DB Revision");

$this->writeProgress("檢查數據庫版本", DeploySource::STEP_CHECK_REVISION);

$totalTargets = count($this->_runSettings['targets']);

// Target Database Revision
foreach($aSettings['targets'] as $target){

$targetAuth = $this->_targetDatabaseMaps[$target]['auth'];
$targetDatabase = $this->_targetDatabaseMaps[$target]['database'];

$currentTargetSeq = array_search($target, $aSettings['targets'])+1;
$this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetDatabase 數據庫版本", DeploySource::STEP_CHECK_REVISION);

$authType = '';
if($targetAuth){
$authType = substr($targetAuth, 0, strpos($targetAuth, '.'));
}

$func = '_checkTargetDBRevision' . ($authType?'_' . $authType : '');
$aRevision = $this->$func($target);

if(!is_array($aRevision) && !in_array($aRevision, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
$this->writeLog("\nTarget[{$target}]: error occured while check  $targetDatabase : " . $this->getErrorMsg());
return false;
}

$this->writeLog("\nTarget[{$target}]: $targetDatabase Rev is: " . $aRevision['revision']);
$this->writeProgress("Target[{$target}]: $targetDatabase 數據庫版本為: " . $aRevision['revision'], DeploySource::STEP_CHECK_REVISION);

$aR['targetRevisions'][$target] = $aRevision;

// 同時保存到$this->_runSettings中
$this->_runSettings['targetRevisions'][$target] = $aRevision;
}

$this->writeLog("\n\nSucc End check DB Revision");

return $aR;
}

protected function _checkTargetDBRevision_ssh($target){

$targetDatabase = $this->_targetDatabaseMaps[$target]['database'];
$targetAuth = $this->_targetDatabaseMaps[$target]['auth'];
$sshServer = substr($targetAuth, 3+1);

if(!$sshServer){
throw new __Exception("Couldn't found ssh server", SCRIPT_ERR_CONFIG);
}

$workingDir = $this->getWorkingDir();

// Gen SQL
//$sql = "SELECT MAX(  `change_number` ) AS revision FROM  `changelog`;";
//$sql = "SELECT * FROM `changelog`;";
$change_table = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table'));
$sql = "SELECT * FROM `" . ($change_table?$change_table:'changelog') . "`";

$sqlFile = "qdbrev_"  . $this->genToken() . ".sql";

file_put_contents($workingDir . '/' . $sqlFile, $sql);

$scp2Dir = DConfig::getConfig(array('deploy.ssh_server_maps', $sshServer, 'tmpdir'));
$scp2File = $scp2Dir . "/" . $sqlFile;

if(!$scp2Dir){
throw new __Exception("Couldn't found ssh server tmpdir", SCRIPT_ERR_CONFIG);
}

$revisionFile = $workingDir . "/" . $target . "_db_revision.xml";

$properties = array();
$properties['sshserver'] = "ssh_{$sshServer}";
$properties['scp.sourceFile'] = $workingDir . "/" .  $sqlFile;
//$properties['scp.sourceDir'] = false;
$properties['scp.toDir'] = $scp2Dir;

//$properties['scp.autocreate'] = 'false';

//$properties['ssh.command'] = 'ls';

// SCP transfer sql file to server
$runOK = $this->_callPhing('scp', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}

// Query DB Revision
$properties = array();
$properties['sshserver'] = "ssh_{$sshServer}";
$properties['dbserver'] = "db_{$target}";
$properties['batch_sql_file'] = $scp2File;
$properties['revision_file'] = $revisionFile;

$runOK = $this->_callPhing('ssh_query_db_revision', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}

// Remove Server SQL File
/*
$properties = array();
$properties['sshserver'] = "ssh_{$sshServer}";
$properties['ssh.command'] = "rm -f $scp2File";

$runOK = $this->_callPhing('ssh', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}*/

if(!file_exists($revisionFile)){
throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG);
}

$revision_result = file_get_contents($revisionFile);

$dbUpdateLogs = mysqlxml2array($revision_result);

$dbRevision = 0;
$dbUpdateTime = 0;
foreach($dbUpdateLogs as $log){
if($log['change_number']>$dbRevision){
$dbRevision = $log['change_number'];
$dbUpdateTime = $log['complete_dt'];
}
}

return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs);
}

protected function _checkTargetDBRevision_api($target){

$targetDatabase = $this->_targetDatabaseMaps[$target]['database'];
$targetAuth = $this->_targetDatabaseMaps[$target]['auth'];
$apiServer = substr($targetAuth, 3+1);

/*
if(!$apiServer){
throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG);
}*/

$workingDir = $this->getWorkingDir();

$revisionFile = $workingDir . "/" . $target . "_db_revision.json";

// Query DB Revision
/*
$properties = array();
$properties['apiserver'] = "api_{$apiServer}";
$properties['revision_file'] = $revisionFile;
$runOK = $this->_callPhing('api_query_db_revision', $target, $properties);
*/

$properties = array();
if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){
foreach($this->_apiServerMaps[$apiServer] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

if(!empty($this->_targetDatabaseMaps[$target]['auth.options'])){
foreach($this->_targetDatabaseMaps[$target]['auth.options'] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

//print_r($properties);exit;

$properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'check'));
$properties['httprequest.responseContent'] = $revisionFile;

$runOK = $this->_callPhing('http_request', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}

if(!file_exists($revisionFile)){
throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG);
}

$revision_result = file_get_contents($revisionFile);

require_once 'Services/JSON.php';
$json = new Services_JSON ();
$dbUpdateLogs = (array) $json->decode($revision_result);

if(!$dbUpdateLogs){
$this->_setErrorMsg($revision_result);
return false;
}

$dbUpdateLogs = $dbUpdateLogs?$dbUpdateLogs:array();

$dbRevision = 0;
$dbUpdateTime = 0;
foreach($dbUpdateLogs as $log){
$log = (array) $log;
if($log['change_number']>$dbRevision){
$dbRevision = $log['change_number'];
$dbUpdateTime = $log['complete_dt'];
}
}

return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs);
}

public function backupDB($aSettings){
$aR = array();

$this->setToken($aSettings['token']);

$this->writeLog("\n\nSTART backup DB");

$this->writeProgress("備份數據庫", self::STEP_BACKUP_BEFORE);

$totalTargets = count($this->_runSettings['targets']);

// Target Database Revision
foreach($aSettings['targets'] as $target){

$targetDatabase = $this->_targetDatabaseMaps[$target]['database'];

$currentTargetSeq = array_search($target, $aSettings['targets'])+1;
$this->writeProgress("开始備份第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetDatabase 數據庫", self::STEP_BACKUP_BEFORE);

$this->writeLog("\nTry to backup Target $target: $targetDatabase");

$runOK = $this->_backupDB($target);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
break;
}
}

return $runOK;
}

protected function _backupDB($target){
$sshServer = '';
if (isset ( $this->_targetDatabaseMaps [$target] ['auth'] )) {
$targetAuth = $this->_targetDatabaseMaps [$target] ['auth'];

if(substr($targetAuth, 0, 4)=='ssh.'){
$sshServer = substr ( $targetAuth, 3 + 1 );
}
}

$properties = array();
$properties['dbserver'] = "db_{$target}";
if($sshServer){
$properties['sshserver'] = "ssh_{$sshServer}";
}

// SCP transfer sql file to server
$runOK = $this->_callPhing('backup_db', $target, $properties);

return $runOK;
}

protected function _runSettings($aSettings){
global $_LOGIN_USER;

$this->_runLED = true;
$this->_runSettings = $aSettings;

$this->_runUser = array();
$this->_runTargets = array();
$this->_runEvent = array();

$projectInfo = array();
$issueInfo = array();

// 添加用户
$oUser = new UserModel();

$userName = $this->getExecutor();

/*
if($userName && !($userInfo=$oUser->userExists($userName))){
$userInfo = array();
$userInfo['user'] = $userName;
$userInfo['password'] = md5('sandbox');

$userInfo['uid'] = $oUser->add($userInfo);
}*/

$userInfo = $this->Runder->HS->getUser();;

$this->_runUser = $userInfo;

// 添加目标
$oTarget = new TargetModel();

foreach($aSettings['targets'] as $target){
if($target && !($targetInfo=$oTarget->targetExists($target))){
// 添加新target
$configIndex = 'deploy.target_' . $aSettings['type'] . '_maps';

$targetInfo = array();
$targetInfo['targetName'] = DConfig::getConfig(array($configIndex, $target, 'desc'));
$targetInfo['target'] = $target;
$targetInfo['targetType'] = $aSettings['type'];
$targetInfo['updateUid'] = $targetInfo['createUid'] = $userInfo['uid'];
$targetInfo['updateTime'] = $targetInfo['createTime'] = time();

$targetInfo['targetId'] = $oTarget->add($targetInfo);
}

$this->_runTargets[$target] = $targetInfo;
}

// 添加事件
$oEvent = new EventModel();

$token = $this->_runSettings['token'];
if($token && !($eventInfo=$oEvent->eventExists($token))){
$eventInfo = array();
$eventInfo['issueId'] = $issueInfo['issueId'];
$eventInfo['projectId'] = $projectInfo['projectId'];

$eventInfo['eventTitle'] = $aSettings['eventTitle'];
$eventInfo['eventToken'] = $aSettings['token'];
$eventInfo['eventType'] = $aSettings['type'];

$eventInfo['sourceRepository'] = $aSettings['repname'];
$eventInfo['sourceBranch'] = $aSettings['branch'];
$eventInfo['sourcePath'] = $aSettings['path'];

$eventInfo['deployStatus'] = DEPLOY_STATUS_WAIT;

$eventInfo['issueCode'] = $aSettings['issueCode'];
$eventInfo['fileLogs'] = $aSettings['updateLogs'];

$eventInfo['updateUid'] = $eventInfo['createUid'] = $userInfo['uid'];
$eventInfo['updateTime'] = $eventInfo['createTime'] = time();

$eventInfo['eventId'] = $oEvent->add($eventInfo);
}

// 添加目标事件
if($this->_runTargets && $eventInfo){
foreach($this->_runTargets as $targetInfo){
if(!($eventTargetInfo=$oEvent->targetExists($eventInfo['eventId'], $targetInfo['targetId']))){
$eventTargetInfo = array();
$eventTargetInfo['eventId'] = $eventInfo['eventId'];
$eventTargetInfo['targetId'] = $targetInfo['targetId'];
$eventTargetInfo['issueId'] = $issueInfo['issueId'];
$eventTargetInfo['target'] = $targetInfo['target'];
$eventTargetInfo['deployStatus'] = DEPLOY_STATUS_WAIT;

$oEvent->addTarget($eventTargetInfo);
}
}
}

$this->_runEvent = $eventInfo;
}

protected function _saveTargetDatabaseRevisions($target, $database, $newRev, $doSQL=NULL, $undoSQL=NULL){
static $oRevision = null;

$user = $this->getExecutor();
$nowTime = time();

if(!$oRevision){
$oRevision = new DatabaseRevisionModel();
}

$this->writeLog("\n\nSTART save database revisions for target: $target");

$data = array(
'db' => $database,
'target' => $target,
'targetId' => $this->_runTargets[$target]['targetId'],
'revision' => $newRev,
'revAuthor' => $user,
'revTime' => $nowTime,
'updateUid' => $this->_runUser['uid'],
'updateTime' => $nowTime,
'lastEventId' => $this->_runEvent['eventId'],
);

$aPrimKeys = array(
'db' => $database,
'target' => $target,
);

$aOldRev = array();

if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){
$data['createUid'] = $data['updateUid'];
$data['createTime'] = $data['updateTime'];
$data['createRev'] = $data['revision'];

$oRevision->add($data);

}elseif ($aOldRev ['revision'] != $newRev) {
$oRevision->save($data);
}

// save to log
// log file with new revision
if ($aOldRev ['revision'] != $newRev) {
$logData = array (
'eventId' => $this->_runEvent ['eventId'],
'targetId' => $this->_runTargets [$target] ['targetId'],
'uid' => $this->_runUser ['uid'],
'user' => $user,
'target' => $target,
'time' => $nowTime,
'db' => $database,
'revision' => $data['revision'],
'revAuthor' => $data['revAuthor'],
'revTime' => $data['revTime'],
'oldRev' => $aOldRev['revision'],
'doSql' => $doSQL,
'undoSql' => $undoSQL,
);
$oRevision->addLog ( $logData );

$this->writeLog("\n database {$database} new revision is {$newRev}");
}else{
$this->writeLog("\n database {$database} has the same revision as old rev: {$aOldRev['revision']}");
}

$this->writeLog("\n\nSucc saved database revisions for target: {$target}");

}

protected function _saveTargetSVNRevisions($target, $svn, $newRev, $updateLog){
static $oRevision = null;

$user = $this->getExecutor();
$nowTime = time();

if(!$oRevision){
$oRevision = new SVNRevisionModel();
}

$this->writeLog("\n\nSTART save svn revisions for target: $target");

$data = array(
'svn' => $svn,
'target' => $target,
'targetId' => $this->_runTargets[$target]['targetId'],
'revision' => $newRev,
'revAuthor' => $user,
'revTime' => $nowTime,
'updateUid' => $this->_runUser['uid'],
'updateTime' => $nowTime,
'lastEventId' => $this->_runEvent['eventId'],
);

$aPrimKeys = array(
'svn' => $svn,
'target' => $target,
);

$aOldRev = array();

if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){
$data['createUid'] = $data['updateUid'];
$data['createTime'] = $data['updateTime'];
$data['createRev'] = $data['revision'];

$oRevision->add($data);

}elseif ($aOldRev ['revision'] != $newRev) {
$oRevision->save($data);
}

// save to log
// log file with new revision
if ($aOldRev ['revision'] != $newRev) {
$logData = array (
'eventId' => $this->_runEvent ['eventId'],
'targetId' => $this->_runTargets [$target] ['targetId'],
'uid' => $this->_runUser ['uid'],
'user' => $user,
'target' => $target,
'time' => $nowTime,
'svn' => $svn,
'revision' => $data['revision'],
'revAuthor' => $data['revAuthor'],
'revTime' => $data['revTime'],
'oldRev' => $aOldRev['revision'],
'updateLog' => $updateLog,
);
$oRevision->addLog ( $logData );

$this->writeLog("\n svn {$svn} new revision is {$newRev}");
}else{
$this->writeLog("\n svn {$svn} has the same revision as old rev: {$aOldRev['revision']}");
}

$this->writeLog("\n\nSucc saved database revisions for target: {$target}");

}

protected function _saveTargetCodeRevisions($target){
static $oRevision = null;

$user = $this->getExecutor();
$nowTime = time();

if(!$oRevision){
$oRevision = new CodeRevisionModel();
}

$this->writeLog("\n\nSTART save code files revisions for target: $target");

$totalFiles = 0;
$newFiles = 0;
$updateFiles = 0;

$this->_writeLog(print_r($this->_runCodeRevisions, true), 'debug');

foreach($this->_runCodeRevisions as $file=>$revInfo){
$data = array(
'file' => $file,
'target' => $target,
'targetId' => $this->_runTargets[$target]['targetId'],
'revision' => $revInfo['revision'],
'revAuthor' => $revInfo['author'],
'revTime' => $revInfo['committime'],
'updateUid' => $this->_runUser['uid'],
'updateTime' => $nowTime,
'lastEventId' => $this->_runEvent['eventId'],
);

$aPrimKeys = array(
'file' => $file,
'target' => $target,
);

$aOldRev = array();

if(!($aOldRev=$oRevision->revisionExists($aPrimKeys))){
$data['createUid'] = $data['updateUid'];
$data['createTime'] = $data['updateTime'];
$data['createRev'] = $data['revision'];

$oRevision->add($data);

$newFiles++;
}elseif ($aOldRev ['revision'] != $revInfo['revision']) {
$oRevision->save($data);
$updateFiles++;
}

// save to log
// log file with new revision
if ($aOldRev ['revision'] != $revInfo['revision']) {
$logData = array (
'eventId' => $this->_runEvent ['eventId'],
'targetId' => $this->_runTargets [$target] ['targetId'],
'uid' => $this->_runUser ['uid'],
'user' => $user,
'target' => $target,
'time' => $nowTime,
'file' => $file,
'revision' => $revInfo['revision'],
'revAuthor' => $revInfo['author'],
'revTime' => $revInfo['committime'],
'oldRev' => $aOldRev['revision'],
);
$oRevision->addLog ( $logData );

$this->writeLog("\n file {$totalFiles}.  {$file} new revision is {$revInfo['revision']}");
}else{
$this->writeLog("\n file {$totalFiles}.  {$file} has the same revision as old file: {$aOldRev['revision']}");
}

$totalFiles++;
}

$this->writeLog("\n\nSucc saved code files revisions for target: {$target}, total files: {$totalFiles}, new added {$newFiles}, old updated {$updateFiles}");

return $totalFiles;

}

public function checkSVNRevision($aSettings){
$aR = array();

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$this->writeLog("\n\nSTART check SVN Revision");

$this->writeProgress("檢查SVN代码版本", DeploySource::STEP_CHECK_REVISION);

$totalTargets = count($this->_runSettings['targets']);

// Target SVN Revision
foreach($aSettings['targets'] as $target){

$targetAuth = $this->_targetSVNMaps[$target]['auth'];
$targetCopy = $this->_targetSVNMaps[$target]['svn'];

$currentTargetSeq = array_search($target, $aSettings['targets'])+1;
$this->writeProgress("开始檢查第 {$currentTargetSeq}/{$totalTargets}: [{$target}] $targetCopy SVN代码版本", DeploySource::STEP_CHECK_REVISION);

$authType = '';
if($targetAuth){
$authType = substr($targetAuth, 0, strpos($targetAuth, '.'));
}

$func = '_checkTargetSVNRevision' . ($authType?'_' . $authType : '');
$aRevision = $this->$func($target);

if(!is_array($aRevision) && !in_array($aRevision, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
$this->writeLog("\nTarget[{$target}]: error occured while check  $targetCopy : " . $this->getErrorMsg());
return false;
}

$this->writeLog("\nTarget[{$target}]: $targetCopy Rev is: " . $aRevision['revision']);
$this->writeProgress("Target[{$target}]: $targetCopy SVN代码版本为: " . $aRevision['revision'], DeploySource::STEP_CHECK_REVISION);

$aR['targetRevisions'][$target] = $aRevision;

// 同時保存到$this->_runSettings中
$this->_runSettings['targetRevisions'][$target] = $aRevision;
}

$this->writeLog("\n\nSucc End check SVN Revision");

return $aR;
}

protected function _checkTargetSVNRevision_api($target){

$targetSVN = $this->_targetSVNMaps[$target]['svn'];
$targetAuth = $this->_targetSVNMaps[$target]['auth'];
$apiServer = substr($targetAuth, 3+1);

/*
if(!$apiServer){
throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG);
}*/

$workingDir = $this->getWorkingDir();

$revisionFile = $workingDir . "/" . $target . "_svn_revision.json";

// Query DB Revision
/*
$properties = array();
$properties['apiserver'] = "api_{$apiServer}";
$properties['revision_file'] = $revisionFile;
$runOK = $this->_callPhing('api_query_db_revision', $target, $properties);
*/

$properties = array();
if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){
foreach($this->_apiServerMaps[$apiServer] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

if(!empty($this->_targetSVNMaps[$target]['auth.options'])){
foreach($this->_targetSVNMaps[$target]['auth.options'] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

//print_r($properties);exit;

$properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'svn', 'cmd'=>'info'));
$properties['httprequest.responseContent'] = $revisionFile;

$runOK = $this->_callPhing('http_request', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}

if(!file_exists($revisionFile)){
throw new __Exception("Revision file not exist: $revisionFile", SCRIPT_ERR_CONFIG);
}

$revision_result = file_get_contents($revisionFile);

require_once 'Services/JSON.php';
$json = new Services_JSON ();
$svnRevision = (array) $json->decode($revision_result);

//print_r($dbUpdateLogs);exit;

if(!$svnRevision){
$this->_setErrorMsg($revision_result);
return false;
}

return $svnRevision;
}

public function migrateVerison($aSettings){

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$this->writeLog("\n\nSTART migrate version");

$this->writeProgress("迁移代码环境", DeploySource::STEP_MIGRATE_VERSION);

$target = $aSettings['migrate'];
$targetAuth = $this->_targetMigrateMaps[$target]['auth'];

$authType = '';
if($targetAuth){
$authType = substr($targetAuth, 0, strpos($targetAuth, '.'));
}

$func = '_migrate' . ($authType?'_' . $authType : '');
$aR = $this->$func($target);

if(!is_array($aR) && !in_array($aR, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
$this->writeLog("\nError occured while migrate  $target : " . $this->getErrorMsg());
return false;
}

$this->writeLog("\nMigrate {$target} result: " . $aR['msg']);
$this->writeProgress("迁移{$target}返回结果: " . $aR['msg'], DeploySource::STEP_MIGRATE_VERSION);

$this->writeLog("\n\nSucc End migrate Version");

return $aR['msg'];
}

protected function _migrate_api($target){

$targetMigrate = $this->_targetMigrateMaps[$target]['migrate'];
$targetAuth = $this->_targetMigrateMaps[$target]['auth'];
$apiServer = substr($targetAuth, 3+1);

/*
if(!$apiServer){
throw new __Exception("Couldn't found api server", SCRIPT_ERR_CONFIG);
}*/

$workingDir = $this->getWorkingDir();

$revisionFile = $workingDir . "/" . $target . "_migrate.json";

// Query DB Revision
/*
$properties = array();
$properties['apiserver'] = "api_{$apiServer}";
$properties['revision_file'] = $revisionFile;
$runOK = $this->_callPhing('api_query_db_revision', $target, $properties);
*/

$properties = array();
if(!empty($apiServer) && !empty($this->_apiServerMaps[$apiServer])){
foreach($this->_apiServerMaps[$apiServer] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

if(!empty($this->_targetMigrateMaps[$target]['auth.options'])){
foreach($this->_targetMigrateMaps[$target]['auth.options'] as $p=>$v){
if(substr($p, 0, 12)!='httprequest.'){
$p = 'httprequest.' . $p;
}
$properties[$p] = $v;
}
}

//print_r($properties);exit;

$properties['httprequest.url'] = url_append_param($properties['httprequest.url'], array('act'=>'migrate', 'migrate'=>$targetMigrate));
$properties['httprequest.responseContent'] = $revisionFile;

$runOK = $this->_callPhing('http_request', $target, $properties);

if(!in_array($runOK, array(DeploySource::STATUS_DONE_SUCCESS, DeploySource::STATUS_DONE_WARNING))){
return $runOK;
}

if(!file_exists($revisionFile)){
throw new __Exception("JSON file not exist: $revisionFile", SCRIPT_ERR_CONFIG);
}

$revision_result = file_get_contents($revisionFile);

require_once 'Services/JSON.php';
$json = new Services_JSON ();
$svnRevision = (array) $json->decode($revision_result);

//print_r($dbUpdateLogs);exit;

if(!$svnRevision){
$this->_setErrorMsg($revision_result);
return false;
}

return $svnRevision;
}
}


DeploySource.class.php   部署

<?php defined('IN_HEAVEN') or die('Hacking Attempt!');

/**
* DeploySource
*
* PHP version 5
*
* @category	4SWeb
* @package     Admin
* @subpackage  Custom
* @author      zhongyiwen
* @version     SVN: $Id: DeploySource.class.php 67 2014-11-25 15:12:29Z blin.z $
*/

class DeploySource{
protected $_logDir;
protected $_enableLog = true;

protected $_targetCodeMaps = array();
protected $_targetDatabaseMaps = array();
protected $_targetSVNMaps = array();
protected $_targetMigrateMaps = array();
protected $_sshServerMaps = array();
protected $_ftpServerMaps = array();

protected $_errorMsgs=array();
protected $_errorFiles=array();
protected $_errorTargets=array();

protected $_finishTargets = array();

protected $_runLED = false;
protected $_runSettings = array();
protected $_runCodeRevisions = array();

protected $_dumpDir;

/**
* STEP consts
*/
const STEP_SVN_COPY = 'SVN_COPY';
const STEP_CHECK_REVISION = 'CHECK_REVISION';
const STEP_PHING_DEPLOY = 'PHING_DEPLOY';
const STEP_VERIFY_DEPLOY = 'VERIFY_DEPLOY';
const STEP_BACKUP_BEFORE = 'BACKP_BEFORE';

const STEP_MIGRATE_VERSION = 'MIGRATE_VERSION';

/**
* Status consts
*/
const STATUS_DONE_SUCCESS = 'DONE_SUCCESS';
const STATUS_DONE_WARNING = 'DONE_WARNING';
const STATUS_STOP_ERROR = 'STOP_ERROR';

public $Runder;

/**
* Regx patterns for parsing update logs
* @var array
*/
protected $_attributePatterns = array(
'revision' => 'r\s*([0-9]+|HEAD)',
);

public function __construct($runder=null){
$this->_logDir = DConfig::getConfig('deploy.log_dir');
$this->_dumpDir = DConfig::getConfig('deploy.dump_working_dir');

$this->_targetCodeMaps = DConfig::getConfig('deploy.target_code_maps');
$this->_targetDatabaseMaps = DConfig::getConfig('deploy.target_database_maps');
$this->_targetSVNMaps = DConfig::getConfig('deploy.target_svn_maps');
$this->_targetMigrateMaps = DConfig::getConfig('deploy.target_migrate_maps');

$this->_ftpServerMaps = DConfig::getConfig('deploy.ftp_server_maps');
$this->_sshServerMaps = DConfig::getConfig('deploy.ssh_server_maps');
$this->_apiServerMaps = DConfig::getConfig('deploy.api_server_maps');

$this->Runder = $runder;
}

public function getErrorMsg($delimiter=" "){
return ($this->_errorMsgs && is_array($this->_errorMsgs))? implode($delimiter , $this->_errorMsgs) : $this->_errorMsgs;
}

public function getErrorMsgs(){
return $this->_errorMsgs;
}

public function getErrorFiles(){
return $this->_errorFiles;
}

public function getErrorTargets(){
return $this->_errorTargets;
}

public function getFinishTargets(){
return $this->_finishTargets;
}

protected function _setErrorMsg($errorMsg){
$this->_errorMsgs[] = $errorMsg;
}

protected function _setErrorFile($file, $status='', $step='', $target='', $remark=''){

$aUpdates = array();
if(is_array($file)) $aUpdates = $file;
else $aUpdates['file'] = $file;

if($status) $aUpdates ['status'] = $status;
if($step) $aUpdates ['step'] = $step;
if($target) $aUpdates ['target'] = $target;
if($remark) $aUpdates ['remark'] = $remark;

$this->_errorFiles[] = $aUpdates;
}

protected function _setErrorTarget($target, $status='', $step='', $remark=''){

$aUpdates = array();
if(is_array($target)) $aUpdates = $target;
else $aUpdates['target'] = $target;

if($status) $aUpdates ['status'] = $status;
if($step) $aUpdates ['step'] = $step;
if($remark) $aUpdates ['remark'] = $remark;

$this->_errorTargets[] = $aUpdates;
}

public function clearErrors(){
$this->clearErrorMsgs();
$this->clearErrorFiles();
$this->clearErrorTargets();
}

public function clearErrorMsgs(){
$this->_errorMsgs = '';
}

public function clearErrorFiles(){
$this->_errorFiles = array();
}

public function clearErrorTargets(){
$this->_errorTargets = array();
}

protected function _getFullPath($file, $dir=''){
if(!$dir){
$dir = DEPLOY_ROOT_DIR;
}

if(substr($file, 0, 1)=='/' || substr($file, 0, 1)=='\\' || preg_match('/^[a-zA-Z]\:/', $file)){
return $file;
}else{
return $dir . "/" . $file;
}
}

public function getLogFile($type=''){
$file = $this->getToken() . ($type?'_' . $type : '') . ".log";
return $this->_getFullPath($file, $this->_logDir);
}

protected function _writeLog($content, $type='', $keepOpen=true){
static $_fps=array();

$file = $this->getLogFile($type);

if(!$this->_enableLog || !$file){
return false;
}

if (!isset($_fps)) $_fps = array();

$signature = serialize($file);
if (!isset($_fps[$signature])) {
$_fps[$signature] = fopen($file, 'a');
}

$fp = $_fps[$signature];
if(!$fp){
throw new __Exception("Couldn't write log file: $file ", SCRIPT_ERR_CONFIG);
}

if (flock($fp, LOCK_EX)) { // do an exclusive lock
fwrite($fp, $content);
flock($fp, LOCK_UN); // release the lock
} else {
throw new __Exception("Couldn't lock the file: $file", SCRIPT_ERR_CONFIG);
}

if(!$keepOpen){
fclose($fp);
unset($_fps[$signature]);
}

return true;
}

public function dumpDirFromSVN($repname, $dir, $revision='HEAD', $save2Dir='') {
global $peg, $rep;

$save2Dir = !empty($save2Dir)?$save2Dir:$this->getWorkingDir();

$dir = str_replace('\\', '/', $dir);
$leadingChar = substr($dir, 0, 1);
if($leadingChar!='/'){
$dir = '/' . $dir;
}

$svnrep = new SVNRepository ( $rep );

// 加--force
//$revision .= " --force";

$result = $svnrep->exportRepositoryPath($dir, $save2Dir, $revision, $peg);

return $result==0?true:false;
}

public function dumpFileFromSVN($repname, $file, $revision = 'HEAD', $save2File=false) {
global $peg, $rep;

static $svnrep = null;

$file = str_replace('\\', '/', $file);
$leadingChar = substr($file, 0, 1);
if($leadingChar!='/'){
$file = '/' . $file;
}

if(!$svnrep){
$svnrep = new SVNRepository ( $rep );
}

ob_start ();
$svnrep->getFileContents ( $file, '', $revision, $peg, '', 'no' );
$content = ob_get_contents ();
ob_end_clean ();

$saveSucc = false;
if($content && $save2File){
$saveSucc = file_put_contents($save2File, $content);
}

return !$save2File?$content:$saveSucc;
}

protected function _isFileLog($file){
$tag = substr($file, -1);

return $tag!='/';
}

public function dumpCodeFromSVN($aParsedLogs, $repname, $branch, $defaultRevision='HEAD') {
global $peg, $rep;

$aErrorLogs = array();

$workingDir = $this->getWorkingDir('code', true);

$svnrep = new SVNRepository ( $rep );

$this->writeLog("\n\nSTART dump files from SVN repository $repname, branch $branch");

$total = 0;
$succ = 0;
$fail = 0;
$fileCounter = 0;

$totalFiles = count($aParsedLogs);

foreach ( $aParsedLogs as $i=>$log ) {
$rev = $defaultRevision;
if (is_array ( $log )) {
$file = $log ['file'];
if ($log ['revision']) {
$rev = $log ['revision'];
}
} else {
$file = $log;
}

if (! $file) {
continue;
}

$total++;

$aUpdates = array ();
$aUpdates ['revision'] = $rev;
if ($this->_isFileLog ( $file )) {
$aUpdates ['file'] = $file;
$path = "/" . $branch . "/" . $file;

$this->writeLog ( "\n[svndump] Will get file {$file} from repository {$repname}, revision {$rev}" );

$content = $this->dumpFileFromSVN ( $repname, $path, $rev, false );

$fileCounter ++;
$this->writeProgress ( "從SVN倉庫獲取第 $fileCounter/$totalFiles 個文件", self::STEP_SVN_COPY );

if ($content) {

$aUpdates ['dumpFile'] = $this->saveCodeFile ( $file, $content );
$this->writeLog ( ", Successfully save as {$aUpdates['dumpFile']}" );

$succ ++;
} else {
$aUpdates ['step'] = self::STEP_SVN_COPY;
$aUpdates ['target'] = 'ALL';
$aUpdates ['status'] = "SVN Error:無法從SVN獲取該文件";
$aUpdates ['remark'] = "請檢查文件是否存在,文件名是否正確,文件版本是否存在(如高於SVN版本)";

$this->writeLog ( ", Failed: couldn't obtain this file" );
$fail ++;

$this->writeLog ( "\nStop dump files from SVN, couldn't obtain $file, please check file name correct or file exists" );

$this->_setErrorMsg ( "SVN Error:無法從SVN獲取文件 {$file},請檢查文件是否存在,或者文件名是否正確" );
$this->_setErrorFile ( $aUpdates );

return DeploySource::STATUS_STOP_ERROR;
}
}else{

$aUpdates ['dir'] = $file;
$path = "/" . $branch . "/" . $file;

$this->writeLog ( "\n[svndump] Will get dir {$file} from repository {$repname}, revision {$rev}" );

$tmpFileDir = '';
if(substr($file, 0, 1)=='/'){
$tmpFileDir = substr($file, 1);
}else{
$tmpFileDir = $file;
}
$fileFolder = substr($tmpFileDir, 0, strrpos($tmpFileDir, '/'));

$save2Dir = $workingDir . "/" . $fileFolder;
$retcode = $this->dumpDirFromSVN( $repname, $path, $rev, $save2Dir);

$aUpdates['dumpFile'] = $save2Dir;

$fileCounter ++;
$this->writeProgress ( "從SVN倉庫獲取第 $fileCounter/$totalFiles 個文件", self::STEP_SVN_COPY );

if ($retcode) {
$this->writeLog ( ", Successfully save as {$aUpdates['dumpFile']}" );

$succ ++;
} else {
$aUpdates ['step'] = self::STEP_SVN_COPY;
$aUpdates ['target'] = 'ALL';
$aUpdates ['status'] = "SVN Error:無法從SVN獲取該目錄";
$aUpdates ['remark'] = "請檢查目錄是否存在,或者目錄名是否正確";

$this->writeLog ( ", Failed: couldn't obtain this file" );
$fail ++;

$this->writeLog ( "\nStop dump files from SVN, couldn't obtain $file, please check directory name correct or directory exists" );

$this->_setErrorMsg ( "SVN Error:無法從SVN獲取目錄 {$file},請檢查目錄是否存在,或者目錄名是否正確" );
$this->_setErrorFile ( $aUpdates );

return DeploySource::STATUS_STOP_ERROR;
}
}

}

$this->writeLog("\nSUCC dump files from SVN, total:$total file(s) succ:$succ fail:$fail");

return DeploySource::STATUS_DONE_SUCCESS;
}

public function checkRevisionFromSVN($aParsedLogs, $repname, $branch, $defaultRevision='HEAD') {
global $peg, $rep;

$aErrorLogs = array();

$svnrep = new SVNRepository ( $rep );

$this->writeLog("\n\nSTART check files' revisions from SVN repository $repname, branch $branch");

$fileCounter = 0;

$totalFiles = count($aParsedLogs);

foreach ( $aParsedLogs as $i=>$log ) {
$rev = $defaultRevision;
if (is_array ( $log )) {
$file = $log ['file'];
if ($log ['revision']) {
$rev = $log ['revision'];
}
} else {
$file = $log;
}

if (! $file) {
continue;
}

$path = "/" . $branch . "/" . $file;
if ($this->_isFileLog ( $file )) {

// store file revision
$revisionInfo = $this->getSVNRevision ( $repname, $path, $rev, true );
if($revisionInfo){
$revisionInfo = (array) $revisionInfo;

// 兼容parsedLogs
$revisionInfo['file'] = $file;
$revisionInfo['revision'] = $revisionInfo['rev'];
$this->_runCodeRevisions [$file] = $revisionInfo;
$this->writeLog ( "\n[checkrevision] file: {$file} new revision is {$revisionInfo['revision']}" );

$fileCounter ++;
}

} else {
// store files revision
$aFileLists = $this->getSVNLists ( $repname, $path );
foreach ( $aFileLists as $list ) {
if ($list ['isDir'] || ! $list ['path'] || ! $list ['revision']) {
continue;
}

$storeFile = str_replace ( '/' . $branch . '/', '', $list ['path'] );
$this->_runCodeRevisions [$storeFile] = $list;

$this->writeLog ( "\n[checkrevision] file: {$storeFile} new revision is {$list['revision']}" );

$fileCounter ++;
}
}
}

$this->writeLog("\nSUCC check total {$fileCounter} files revisions");

return DeploySource::STATUS_DONE_SUCCESS;
}

/**
* Parse Update Logs to array form
* @param string $content
* @return array
*/
public function parseUpdateLogs($content) {

$aUpdateFiles = array();

$lines = explode("\n", $content);

foreach ( $lines as $line ) {
// Skip lines containing EOL only
if (($line = trim ( $line )) == '')
continue;

// skipping update comments
if (substr ( ltrim ( $line ), 0, 1 ) == '#')
continue;

// check extra attributes
$aParsedAttributes = array();
if(($attrPos=strpos($line, '|'))!==false){
$attributes = substr($line, $attrPos+1);
$aParsedAttributes = $this->_parseUpdateAttributes($attributes);

$file = substr( $line, 0, $attrPos);
$file = trim($file);

}else{
$file = $line;
}

$file = str_replace('\\', '/', $file);

// strip leading slash
$firstChar = substr($file, 0, 1);
if($firstChar=='/' || $firstChar=='\\'){
$file = substr($file, 1);
}

if(substr($file, -2)=='/*'){
$file = substr($file, 0, -1);
}

// replace backslash to slash
$file = str_replace("\\", "/", $file);
$fileIdx = md5($file);

if($aParsedAttributes){
$aUpdateFiles[$fileIdx] = array_merge(array('file'=>$file), $aParsedAttributes);
}else{
$aUpdateFiles[$fileIdx] = $file;
}
}

return $aUpdateFiles;
}

/**
* Parse Update Attributes by Regx
*
* @param string|array $attributes
* @return boolean|array
*/
protected function _parseUpdateAttributes($attributes){
$aPatterns = $this->_attributePatterns;
$aStrAttributes = is_array($attributes)?$attributes:explode('|', $attributes);

if(!$aStrAttributes){
return false;
}

$aAttributes = array();

foreach($aStrAttributes as $attr){
$attr = trim($attr);

if(!$attr){
continue;
}

foreach($aPatterns as $name=>$pattern){
if(!$pattern){
continue;
}

if(preg_match('/' . $pattern . '/', $attr, $matches)){
$aAttributes[$name] = $matches[1];
}
}
}

return $aAttributes;
}

public function getWorkingDir($subFolder='', $autoCreate=false){
$workingFolder = $this->getToken();
$workingDir = $this->_dumpDir . "/" . $workingFolder;

if(!file_exists($workingDir)){
mkdir($workingDir);
}

if($subFolder){
$workingDir = $this->_dumpDir . "/" . $workingFolder . "/" . $subFolder;

if($autoCreate && !file_exists($workingDir)){
mkdir($workingDir);
}
}

return $workingDir;
}

public function cleanWorkingDir(){
$workingDir = $this->getWorkingDir();

if(file_exists($workingDir)){

$this->writeLog("\nClean working dir $workingDir");

File_Func::removeDir($workingDir, true);
}

/*
$progressFile = $this->getLogFile('progress');
if(file_exists($progressFile)){
$this->writeProgress('', '', false);
unlink($progressFile);
}*/

}

public function saveCodeFile($file, $content){

$workingDir = $this->getWorkingDir('code');

$fileDir = dirname($file);
Generator::genDir($workingDir, $fileDir);

$dumpFile = $workingDir . "/" . $file;
file_put_contents($dumpFile, $content);

return $dumpFile;
}

public function getExecutor(){
//$authUser = !empty($_SERVER['PHP_AUTH_USER'])?$_SERVER['PHP_AUTH_USER']:(!empty($_SERVER['REMOTE_USER'])?$_SERVER['REMOTE_USER']:'');
$authUser = $this->Runder->loadSession('user')->getUser('user');

return $authUser;
}

public function checkPerm($type){
return $this->Runder->loadSession('user')->checkPerm($type);
}

public function genToken(){

//$dateTime = date('YmdHi');
$dateTime = date('Ymd\_His');

$token = $this->getExecutor() . $dateTime;
$token = str_replace(array('\\', '/', ':', '*', '?', '"', '<', '>', '|'), '', $token);

return $token;
}

public function getToken(){
return $this->_runSettings['token'];
}

public function setToken($token){
return $this->_runSettings['token'] = $token;
}

public function downloadLog($settings=array()){
$this->_runSettings = $settings;

//session_write_close();

// get deploy log
$file = $this->getLogFile($this->_runSettings['type']);
if (file_exists ( $file )) {
header ( "Content-type: text/plain; charset=UTF-8" ); /* or whatever type */
header ( "Content-Disposition: attachment; filename=" . basename($file) );
header ( "Content-Length: " . filesize($file) );
header ( "Pragma: no-cache" );
header ( "Expires: 0" );

$file_contents = file_get_contents ( $file );
print ($file_contents) ;
}else{
print "File not exists";
}
}

public function viewLog($settings=array()){
$this->_runSettings = $settings;

//session_write_close();

// get deploy log
$file = $this->getLogFile($this->_runSettings['type']);
if (file_exists ( $file )) {
$file_contents = file_get_contents ( $file );

$html = <<<EOT
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>View Deploy Log</title>
</head>
<body>
EOT;
$downloadParams = array('act'=>'download', 'type'=>$this->_runSettings['type'], 'token'=>$this->_runSettings['token']);
$downloadLink = 'log.php?' . http_build_query($downloadParams);
$html .= "<p>【<a href='{$downloadLink}'>下載日誌</a>】</p>";
$html .= "<pre>" . $file_contents . "</pre>" ;

$html .= "</body></html>";

print $html;

}else{
print "Log File not exists";
}
}

public function trackProgress($settings=array()){
$aProgress = array();
$aProgress['progress'] = '';
$aProgress['files'] = array();

$this->_runSettings = $settings;

// get deploy log
$file = $this->getLogFile($this->_runSettings['type']);

if(file_exists($file)){
$aProgress['files'] = tailFile($file, 10, true);
}

$progressFile = $this->getLogFile('progress');
if(file_exists($progressFile)){
$aProgress['progress'] = file_get_contents($progressFile);
}

return $aProgress;
}

public function writeProgress($progress, $step='', $keepOpen=true){
static $_fps = array();

if($step && !empty($this->_runSettings['deploySteps'])){
$nowStep = array_search($step, $this->_runSettings['deploySteps'])+1;
$totalSteps = count($this->_runSettings['deploySteps']);

$content = "[Step $nowStep/$totalSteps] $progress";
}else{
$content = $progress;
}

$file = $this->getLogFile('progress');

if(!$this->_enableLog || !$file){
return false;
}

if (!isset($_fps)) $_fps = array();

$signature = serialize($file);
if (!isset($_fps[$signature])) {
$_fps[$signature] = fopen($file, 'w');
}

$fp = $_fps[$signature];
if(!$fp){
throw new __Exception("Couldn't write progress file: $file ", SCRIPT_ERR_CONFIG);
}

if (flock($fp, LOCK_EX)) { // do an exclusive lock
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, $content);
flock($fp, LOCK_UN); // release the lock
} else {
throw new __Exception("Couldn't lock the file: $file", SCRIPT_ERR_CONFIG);
}

if(!$keepOpen){
fclose($fp);
unset($_fps[$signature]);
}
}

public static function getTargetHints($targets, $type='code', $returnArray=false) {
$aServerHints = array ();

switch($type){
case 'code':
$mapName = 'deploy.target_code_maps';
$hintName = 'dir';
break;

case 'database':
$mapName = 'deploy.target_database_maps';
$hintName = 'database';
break;
case 'svn':
$mapName = 'deploy.target_svn_maps';
$hintName = 'svn';
break;
default:
throw new __Exception("Unknown type: $type", SCRIPT_ERR_CONFIG);
}

foreach ( $targets as $idx => $target ) {
if (! DConfig::issetConfig ( array (
$mapName,
$target
) )) {
$vars ['warning'] = "$target 目標服務器未配置";
break;
}

$toDir = DConfig::getConfig ( array (
$mapName,
$target,
$hintName
) );

if (! $toDir) {
$vars ['warning'] = "$target 目標服務器未正確配置部署目錄";
break;
}

$aServerHints [] = ($idx + 1) . ". [" . $target . "] " . $toDir;
}

return $returnArray?$aServerHints:implode(" ", $aServerHints);
}

public function checkDeltaRevision($aSettings){
global $peg, $rep, $rev, $vars;

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$aR = array();

// Delta Files Revision
$aR['deltaRevision'] = 0;
$aR['deltaFiles'] = array();

$deltaRevPattern = '^([0-9]+)';

$this->writeLog("\n\nSTART check SQL Delta Revision");

$this->writeProgress("開始檢查SVN SQL版本", self::STEP_CHECK_REVISION);

$svnrep = new SVNRepository ( $rep );

$aSVNList = $svnrep->getList("/" . $aSettings['path'], $rev, $peg);

if(!$aSVNList){
$this->_setErrorMsg($vars['error']);
return false;
}

foreach($aSVNList->entries as $e){
$f = $e->file;

if($f && preg_match("/{$deltaRevPattern}/", $f, $m)){
$r = intval($m[1]);

if($r){
$aR['deltaFiles'][] = array('file' => $f, 'revision' => $r, 'author' => $e->author, 'date' => $e->date);

if($r > $aR['deltaRevision']){
$aR['deltaRevision'] = $r;
}
}
}
}

return $aR;
}

public function checkCodeLogRevision($aSettings){
global $peg, $rep, $rev, $vars;

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$aR = array();

// Delta Files Revision
$aR['logRevision'] = 0;
$aR['logFiles'] = array();

$deltaRevPattern = '^([0-9]+)';

$this->writeLog("\n\nSTART check code log revision");

$this->writeProgress("开始检查代码更新日志", self::STEP_CHECK_REVISION);

$svnrep = new SVNRepository ( $rep );

$aSVNList = $svnrep->getList("/" . $aSettings['path'], $rev, $peg);

if(!$aSVNList){
$this->_setErrorMsg($vars['error']);
return false;
}

foreach($aSVNList->entries as $e){
$f = $e->file;

if($f && preg_match("/{$deltaRevPattern}/", $f, $m)){
$r = intval($m[1]);

if($r){
$aR['logFiles'][] = array('file' => $f, 'revision' => $r);

if($r > $aR['logRevision']){
$aR['logRevision'] = $r;
}
}
}
}

return $aR;
}

public function checkSVNLogRevision($aSettings){
global $peg, $rep, $rev, $vars;

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$aR = array();

// Delta Files Revision
$aR['headRevision'] = 0;
$aR['revHistories'] = array();

$deltaRevPattern = '^([0-9]+)';

$this->writeLog("\n\nSTART check svn log revision");

$this->writeProgress("开始检查代码版本日志", self::STEP_CHECK_REVISION);

$svnrep = new SVNRepository ( $rep );

// 分支
$aSVNList = $svnrep->getList("/", $rev, $peg);

$aBranches = array();
foreach($aSVNList->entries as $e){
if($e->isdir){
$aBranches[] = array(
'rev' => $e->rev,
'author' => $e->author,
'date' => $e->date,
'file' => $e->file,
);
}
}

usort($aBranches, create_function('$a,$b', 'return $a["rev"]==$b["rev"]?0:($a["rev"]>$b["rev"]?1:-1);'));

$aR['svnBranches'] = $aBranches;

$brev = '';
$erev = 1;
$limit = 100;
$aSVNList = $svnrep->getLog("/" . $aSettings['path'], $brev, $erev, false, $limit);

if(!$aSVNList){
$this->_setErrorMsg($vars['error']);
return false;
}

$title_length = 40;

foreach($aSVNList->entries as $e){
$r = intval($e->rev);
if($r){
$msg = '';
$title = '';
if($e->msg){
$msg = $e->msg;
$msg = htmlspecialchars($msg);
$msg = str_replace(array("\r", "\n"), " ", $msg);
$msg = str_replace("\"", """, $msg);
$msg = str_replace("'", "'", $msg);
//$msg = mb_eregi_replace("\"", """, $msg);
//$msg = mb_eregi_replace("'", "'", $msg);

$title = $msg;
}

if($title && mb_strlen($title)>$title_length){
$title = mb_strimwidth($title, 0, $title_length, '...');
}

$aR['revHistories'][] = array(
'rev' => $r,
'author' => $e->author,
'date' => $e->date,
'msg' => $msg,
'title' => $title,
);

if($r > $aR['headRevision']){
$aR['headRevision'] = $r;
}
}
}

return $aR;
}

public function checkLogFileRevision($aSettings){
global $peg, $rep, $rev, $vars;

$this->_runSettings = $aSettings;
$this->setToken($aSettings['token']);

$aR = array();

// Delta Files Revision
$aR['logFile'] = $_REQUEST['file'];
$aR['logContent'] = '';

$this->writeLog("\n\nSTART check log file revision");

$this->writeProgress("开始检查代码更新日志", self::STEP_CHECK_REVISION);

if(!$aR['logFile']){
$this->_setErrorMsg("未指定代码更新Log文件");
return false;
}

//$svnrep = new SVNRepository ( $rep );

$aR['logContent'] = $this->dumpFileFromSVN($rep, "/" . $aSettings['path'] . "/" . $aR['logFile']);

if(!$aR['logContent']){
$this->_setErrorMsg("未能获取到代码更新Log文件内容");
return false;
}

$aR['logTitle'] = $this->_matchUpdateLogContent($aR['logContent'], 'TITLE');
$aR['logIssueID'] = $this->_matchUpdateLogContent($aR['logContent'], 'ISSUEID');
$aR['logBugID'] = $this->_matchUpdateLogContent($aR['logContent'], 'BUGID');
$aR['logDesc'] = $this->_matchUpdateLogContent($aR['logContent'], 'DESC');

return $aR;
}

protected function _matchUpdateLogContent($content, $token){
$start = "[{$token}]";
$end = "[/{$token}]";

$qstr = "(?:" . preg_quote($start, '~') . "[^\[]*(?:\[[^\/]*)*" . preg_quote($end, '~') . ")";
$pattern = "~#({$qstr})#~";
if(preg_match($pattern, $content, $matches)){
$mt = $matches[1];
$mt = str_replace($start, '', $mt);
$mt = str_replace($end, '', $mt);
return trim($mt);
}else{
return '';
}
}

protected function _checkTargetDBRevision($target) {
$dbConf = DConfig::getConfig(array('deploy.target_database_maps', $target));
if(empty($dbConf)){
throw new __Exception("Target $target Database conf is empty", SCRIPT_ERR_CONFIG);
}

$dbRevision = 0;
$dbUpdateTime = '';
$dbUpdateLogs = array();

// Connecting, selecting database
$link = mysql_connect ( $dbConf['host'] . ($dbConf['port']?":".$dbConf['port']:""), $dbConf['username'], $dbConf['password'] ) or die ( 'Could not connect: ' . mysql_error () );
mysql_select_db ( $dbConf['database'] ) or die ( 'Could not select database' );

$change_table = DConfig::getConfig(array('deploy.target_database_maps', $target, 'changelog_table'));

// Performing SQL query
$query = 'SELECT * FROM `' . ($change_table?$change_table:'changelog') . '` ORDER BY change_number';
$result = mysql_query ( $query ) or die ( 'Query failed: ' . mysql_error () );

while ( $row = mysql_fetch_array ( $result, MYSQL_ASSOC ) ) {
if($row['change_number'] > $dbRevision){
$dbRevision = $row['change_number'];
$dbUpdateTime = $row['complete_dt'];
}

$dbUpdateLogs[] = $row;
}

// Free resultset
mysql_free_result ( $result );

// Closing connection
mysql_close ( $link );

return array('revision' => $dbRevision, 'updated' => $dbUpdateTime, 'logs' => $dbUpdateLogs);
}

protected function _showDirFiles($svnrep, $subs, $level, $limit, $rev, $peg, $listing, $index, $treeview = true) {
global $rep, $peg, $passRevString;

$path = $subs;

// check if path is a file
$pathIsFile = false;
if(preg_match('/\.[a-zA-Z0-9]{1,6}$/', $path)){
$pathIsFile = true;
}

if (!$treeview) {
$level = $limit;
}

// TODO: Fix node links to use the path and number of peg revision (if exists)
// This applies to file detail, log, and RSS -- leave the download link as-is
/*
for ($n = 0; $n <= $level; $n++) {
$path .= $subs[$n].'/';
}*/

// List each file in the current directory
$loop = 0;
$last_index = 0;
$accessToThisDir = $rep->hasReadAccess($path, false);

// If using flat view and not at the root, create a '..' entry at the top.
/*
if (!$treeview && count($subs) > 2) {
$parentPath = $subs;
unset($parentPath[count($parentPath) - 2]);
$parentPath = implode('/', $parentPath);
if ($rep->hasReadAccess($parentPath, false)) {
$listing[$index]['rowparity'] = $index % 2;
$listing[$index]['path'] = $parentPath;
$listing[$index]['filetype'] = 'dir';
$listing[$index]['filename'] = '..';
$listing[$index]['level'] = 0;
$listing[$index]['node'] = 0; // t-node
$listing[$index]['revision'] = $rev;
$index++;
}
}*/

$openDir = false;
$logList = $svnrep->getList($path, $rev, $peg);
if ($logList) {
foreach ($logList->entries as $entry) {
$isDir = $entry->isdir;
/*
if (!$isDir && $level != $limit) {
continue; // Skip any files outside the current directory
}*/
$file = $entry->file;

// Only list files/directories that are not designated as off-limits
$access = ($isDir) ? $rep->hasReadAccess($pathIsFile?$path:$path.$file, true)
: $accessToThisDir;
if ($access) {
$listing[$index]['rowparity'] = $index % 2;

if ($isDir) {
$listing[$index]['filetype'] = ($openDir) ? 'diropen' : 'dir';
//$openDir = isset($subs[$level + 1]) && (!strcmp($subs[$level + 1].'/', $file) || !strcmp($subs[$level + 1], $file));
$openDir = true;
} else {
$listing[$index]['filetype'] = strtolower(strrchr($file, '.'));
$openDir = false;
}
$listing[$index]['isDir'] = $isDir;
$listing[$index]['openDir'] = $openDir;
$listing[$index]['level'] = ($treeview) ? $level : 0;
$listing[$index]['node'] = 0; // t-node
$listing[$index]['path'] = $pathIsFile?$path:$path.$file;
$listing[$index]['filename'] = $file;

$listing[$index]['committime'] = $entry->committime;
$listing[$index]['revision'] = $entry->rev;
$listing[$index]['author'] = $entry->author;
$listing[$index]['age'] = $entry->age;
$listing[$index]['date'] = $entry->date;

$loop++;
$index++;
$last_index = $index;

//if ($isDir && ($level != $limit)) {
if ($isDir) {
// @todo remove the alternate check with htmlentities when assured that there are not side effects
//if (isset($subs[$level + 1]) && (!strcmp($subs[$level + 1].'/', $file) || !strcmp(htmlentities($subs[$level + 1], ENT_QUOTES).'/', htmlentities($file)))) {
if($openDir){

//$listing = $this->showDirFiles($svnrep, $subs, $level + 1, $limit, $rev, $peg, $listing, $index);
$listing = $this->_showDirFiles($svnrep, $path.$file, $level + 1, $limit, $rev, $peg, $listing, $index);
$index = count($listing);
}
}
}
}
}

// For an expanded tree, give the last entry an "L" node to close the grouping
if ($treeview && $last_index != 0) {
$listing[$last_index - 1]['node'] = 1; // l-node
}

return $listing;
}

protected function _showTreeDir($svnrep, $path, $rev, $peg, $listing) {

$subs = explode('/', $path);

// For directory, the last element in the subs is empty.
// For file, the last element in the subs is the file name.
// Therefore, it is always count($subs) - 2
$limit = count($subs) - 2;

return $this->_showDirFiles($svnrep, $path, 0, $limit, $rev, $peg, $listing, 0, false);
}

public function getSVNLists($repname, $dir, $revision='HEAD'){
global $peg, $rep;

static $svnrep = null;

$dir = str_replace('\\', '/', $dir);
$leadingChar = substr($dir, 0, 1);
if($leadingChar!='/'){
$dir = '/' . $dir;
}

// check if path is a file
$tailingChar = substr($dir, -1);
if($tailingChar!='/' && !preg_match('/\.[a-zA-Z0-9]{1,6}$/', $dir)){
$dir .= '/';
}

$aFileLists = array();

if(!$svnrep){
$svnrep = new SVNRepository ( $rep );
}

return $this->_showTreeDir($svnrep, $dir, $revision, $peg, $aFileLists);
}

public function getSVNRevision($repname, $path, $revision='HEAD', $returnDetail=false){
global $peg, $rep;

static $svnrep=null;

$path = str_replace('\\', '/', $path);
$leadingChar = substr($path, 0, 1);
if($leadingChar!='/'){
$path = '/' . $path;
}

// check if path is a file
$tailingChar = substr($path, -1);
if($tailingChar!='/' && !preg_match('/\.[a-zA-Z0-9]{1,6}$/', $path)){
$path .= '/';
}

if(!$svnrep){
$svnrep = new SVNRepository ( $rep );
}

$headlog = $svnrep->getLog($path, $revision, '', true, 1);

if(!$returnDetail){
$headrev = ($headlog && isset($headlog->entries[0])) ? $headlog->entries[0]->rev : 0;
return $headrev;
}else{
return $headlog->entries[0];
}
}

public function writeLog($content, $type='', $keepOpen=true){
return $this->_writeLog($content, $type?$type:$this->_runSettings['type'], $keepOpen);
}

protected function _checkTargetSVNRevision($target) {
$conf = DConfig::getConfig(array('deploy.target_svn_maps', $target));
if(empty($conf)){
throw new __Exception("Target $target SVN conf is empty", SCRIPT_ERR_CONFIG);
}

global $config;

$cmdString = $config->getSvnCommand() . ' info "' . $conf['path'] . '" --xml 2>&1';

exec($cmdString, $cmdOutput, $cmdStatus);

$cmdOutput = implode("", $cmdOutput);
$cmdOutput = trim($cmdOutput);

if(($pos=stripos($cmdOutput, "<?xml"))){
$error = substr($cmdOutput, 0, $pos);
//$xml = substr($output, $pos);
$xml = '';
}else{
$error = false;
$xml = $cmdOutput;
}

if (! $error) {
$data = ( array ) simplexml_load_string ( $xml, 'SimpleXMLElement', LIBXML_NOCDATA );

$entry = ( array ) $data ['entry'];

$repository = (array) $entry['repository'];
$commit = ( array ) $entry ['commit'];
$attributes = (array) $commit['@attributes'];

$revision = $attributes ['revision'];
$branch = str_replace($repository['root'], "", $entry['url']);

$return = array (
'revision' => $revision,
'author' => $commit ['author'],
'date' => $commit ['date'],
'branch' => $branch,
);
}else{
$return = array(
'revision' => $error,
'author' => '',
'date' => '',
'branch' => '',
);
}

return $return;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: