#include "CacheDelete.h"

#include <errno.h>
#include <string.h>

#include "Logger.h"
#include "Database.h"
#include "Util.h"
#include "ServiceConfig.h"


// Query 문장 저장을 위한 Buffer 크기
#define DEFAULT_QUERY_BUFFER_SIZE 512

// sec 를 micro-sec 로 단위 변환시 환산 계수.
#define SEC_TO_MICROSEC          (1000000LL)

// 삭제 작업시 시작 시간을 설정하기 위한 보정값.
#define CACHE_DELETE_RANGE	            (60*60*48)  // 48시간

// 삭제 주기 ( sec 단위)
#define CAHCE_DEL_CHECK_TERM_SEC		(20 * 60)   // 20분


// 생성자
CCacheDelete::CCacheDelete()	
{
	m_threadHandle = 0;

	// NEW 2014-06-16 신규 멤버 변수 초기화 코드 추가.
	// Init() 에서 RCDB 접속 실패가 발생 가능하므로.. 구형 RCDB 형상을 사용토록 설정.
	m_bServiceTableSeparate = false;
}

// 소멸자
CCacheDelete::~CCacheDelete()
{
	// Thread 동작 정지 처리
	// - 만약 Thread 가 이미 종료된 경우 m_threadHandle 이 다른 Thread Handle 일 수 있으므로 
	//   업무 Flow 수정시 주의할 것
	if( m_threadHandle != 0 )
		pthread_cancel( m_threadHandle );    
}

// 객체 초기화 수행
bool CCacheDelete::Init()
{
	// RCDB 접속 정보 Load.
	m_rcdbInfo.Load();

	// RCDB 접속 처리를 위한 객체 생성
	DataBase rcdb;

	// RCDB 접속 시도 및 접속 실패시
	if( rcdb.PgOpenDB( m_rcdbInfo.m_strRcdbIp, m_rcdbInfo.m_nRcdbPort
		, m_rcdbInfo.m_strRcdbName, m_rcdbInfo.m_strRcdbAcct
		, m_rcdbInfo.m_strRcdbAcctPw ) == NULL )
	{
		LOG( LERR, "RCDB connection failed." );
		return false;
	}
	else
	{
		// 접속 성공한 경우...
		LOG( LINF, "RCDB connection test ok..." );

		// NEW 2014-06-23 huibong
		// 서비스 table 분리된 신규 형상인지.. 구형 형상인지 체크하여
		// m_bServiceTableSeparate 변수 설정 처리.
		// 해당 변수 초기화는 생성자에서 1차로 수행함.
		char szQuery[1024];
		snprintf( szQuery, 1023,
			"SELECT * FROM pg_tables WHERE schemaname = 'public' AND tablename = 't_dav_resource' " );

		rcdb.PgDoExec( szQuery );
		if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
		{
			// Query 수행 결과 오류 발생시...
			// - pg_tables 가 존재하지 않을 경우 오류 발생 가능...
			// - 이는 RCDB 가 아직 적절하게 구성되지 않았다는 의미이므로... Init()함수를 오류로 처리한다.
			LOG( LERR, "RCDB t_dav_resource exist check failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

			rcdb.PgClear();
			rcdb.PgCloseDB();

			return false;
		}
		else
		{
			// Query 수행이 정상인 경우.
			int  nResult = rcdb.GetNoTuples();
			if( nResult > 0 )
			{
				// t_dav_resource 테이블이 존재하는 경우...
				m_bServiceTableSeparate = false;
				_LOG( LINF, "CCacheDelete: t_dav_resource table exist.[Old RCDB Type]" );
			}
			else
			{
				// t_dav_resource 테이블이 존재하지 않는 서비스별 테이블 분리 형상인 경우.
				m_bServiceTableSeparate = true;
				_LOG( LINF, "CCacheDelete: t_dav_resource table not exist.[New RCDB Type]" );
			}
		}

		// RCDB 조회 결과 set 를 clear 처리 후 RCDB 접속 해제.
		rcdb.PgClear();
		rcdb.PgCloseDB();

		return true;
	}
}

// thread를 생성하여 업무 flow 를 시작.
bool CCacheDelete::Start()
{
	int nRet = ::pthread_create( &m_threadHandle, NULL, CCacheDelete::threadFunc, this );
	if( nRet != 0 )
	{
		// Thread 생성 실패시
		int errorNum = errno;
		LOG( LERR, "Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

void * CCacheDelete::threadFunc( void * arg )
{
	CCacheDelete* pObject = reinterpret_cast<CCacheDelete *>( arg );
	pthread_detach( pthread_self() );

	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		sleep( CAHCE_DEL_CHECK_TERM_SEC );
	}

	// NEW 2014-06-23 huibong
	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;

	// CHG 2014-06-23 huibong
	// detach 된 Thread 에 대해 pthread_exit() 호출시
	// 기준 할당 자원에 대한 해지 처리가 되지 않아 메모리 누수 발생 가능( FreeBSD ftsd 발생)
	// 따라서 이를 해결하기 detach 된 thread 에 대해 pthread_exit() 호출 코드 제거.
	//pthread_exit(NULL);
	return NULL;
}

// 스레드 내에서 각 loop 단위 처리 호출 함수.
void CCacheDelete::Execute()
{
	// 구형 t_dav_resource 테이블 사용시
	if( m_bServiceTableSeparate == false )
	{
		DeleteFromOldDB();
	}
	else
	{
		// 신형 RCDB 형상 사용시
		DeleteFromDB();
	}
}


// RCDB t_dav_resource 테이블에서 유효기간이 지난 Hot Content Cache Content 를 삭제 처리
// - 구형 RCDB 지원 목적
bool CCacheDelete::DeleteFromOldDB()
{
	// 1. 현재 시간정보를 구한다.( micro-second 단위)
	long long timeNow = longtime_now();
	if( timeNow < 0 )
	{
		LOG( LERR, "longtime_now function error [%lld]", timeNow );
		return false;
	}

	// 2. RCDB 와 연결 처리.
	DataBase rcdb;
	if( rcdb.PgOpenDB( m_rcdbInfo.m_strRcdbIp, m_rcdbInfo.m_nRcdbPort
		, m_rcdbInfo.m_strRcdbName, m_rcdbInfo.m_strRcdbAcct
		, m_rcdbInfo.m_strRcdbAcctPw ) == NULL )
	{
		// RCDB 접속 실패시
		LOG( LERR, "RCDB connection failed." );		
		return false;
	}

	// 3. t_dav_resource 테이블에서 유효기간이 지난 Cache Content 를 삭제 상태로 update 처리

	// Query 문장 저장을 위한 버퍼 생성
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];
	int nResult = 0;

	// 처리 대상 시간 구간 계산 (micro-second 단위 )
	long long timeStart;
	long long timeEnd;

	// 현재시간 기준으로 Hot Content Cache 의 보존 만료 시간을 계산.
	timeEnd = timeNow - ( CServiceConfig::GetInstance()->GetCacheFileTimeout() * SEC_TO_MICROSEC );

	// 조회 시작 시간을 계산.
	// - 본 값이 없을 경우... RCDB를 Full Scan 해야 하므로.. RCDB 에 부하 발생 가능.
	// - 따라서 보존 만료 시간 이전 48 시간 정도로 조회 범위를 감소시킨다.
	timeStart = timeNow - ( ( CServiceConfig::GetInstance()->GetCacheFileTimeout() + CACHE_DELETE_RANGE ) * SEC_TO_MICROSEC );
			
	snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
			"UPDATE t_dav_resource SET deleted_yn = 'Y' ,get_lastmodified = %lld "
			"WHERE is_cache = 'Y' AND deleted_yn = 'N' "
			  "AND get_lastmodified >= '%lld' "
			  "AND get_lastmodified <= '%lld';"
			, timeNow, timeStart, timeEnd
			);
    
	// Query 실행 
	rcdb.PgDoExec(szQuery);

	// Update 처리 결과 Row 수 확인.
	nResult = rcdb.GetCmdTuples();

	if( nResult > 0 )
	{
		_LOG( LINF, "CCacheDelete: t_dav_resource delete count[%d]", nResult );
	}

	rcdb.PgClear();
	rcdb.PgCloseDB();    
	
	return true;
}

// RCDB t_meta_xxx 테이블에서 유효기간이 지난 Hot Content Cache Content 를 삭제 처리        
// - 신형 RCDB 지원 목적
bool CCacheDelete::DeleteFromDB()
{
	// 1. 현재 시간정보를 구한다.( micro-second 단위)
	long long timeNow = longtime_now();
	if( timeNow < 0 )
	{
		LOG( LERR, "longtime_now function error [%lld]", timeNow );
		return false;
	}

	// 2. RCDB 와 연결 처리.
	DataBase rcdb;
	if( rcdb.PgOpenDB( m_rcdbInfo.m_strRcdbIp, m_rcdbInfo.m_nRcdbPort
		, m_rcdbInfo.m_strRcdbName, m_rcdbInfo.m_strRcdbAcct
		, m_rcdbInfo.m_strRcdbAcctPw ) == NULL )
	{
		// RCDB 접속 실패시
		LOG( LERR, "RCDB connection failed." );
		return false;
	}

	// Query 문장 저장을 위한 버퍼 생성
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];
	int nResult = 0;


	// 3. RCDB t_sms_sp_svc_product 테이블에 저장된 서비스 정보를 조회한다.
	snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
		     "SELECT sp_svc_tran_id FROM t_sms_sp_svc_product ORDER BY sp_svc_tran_id DESC "
		    );

	// Query 실행 
	rcdb.PgDoExec( szQuery );
	nResult = rcdb.PgResult( DataBase::NOT_CLEAR );
	if( nResult < 0 )
	{
		// Query 오류 발생시
		// - RCDB 연결 실패 또는
		// - t_sms_sp_svc_product 테이블이 존재하지 않을 경우.		
		LOG( LERR, "RCDB t_sms_sp_svc_product query error.[%s][%s]"
			, rcdb.GetErrorMessage().c_str(), szQuery );

		rcdb.PgClear();
		rcdb.PgCloseDB();
		
		return false;
	}

	// 조회된 각 고객사 정보 확인
	nResult = rcdb.GetNoTuples();
	if( nResult <= 0 )
	{
		// 고객사가 존재하지 않는 경우
		_LOG( LINF, "CCacheDelete: service not found from t_sms_sp_svc_product.[%d]"
			, nResult );

		rcdb.PgClear();
		rcdb.PgCloseDB();
		return true;
	}

	// 조회된 고객사 정보를 변수에 저장.
	std::vector<std::string> vecService;
	std::string strTemp;
	for( int i = 0; i < nResult; i++ )
	{
		strTemp = rcdb.GetValue( i, 0 );		
		if( strTemp.size() > 0 )
			vecService.push_back( strTemp );		
	}
	
	rcdb.PgClear();

	// 변수에 저장된 서비스 정보의 수를 최종 확인
	if( vecService.empty() == true )
	{
		// 유효한 서비스 정보가 존재하지 않는 경우.
		_LOG( LINF, "CCacheDelete: service not found from t_sms_sp_svc_product." );
		rcdb.PgCloseDB();
		return true;
	}
		
	// 4. 루프를 돌면서 
	// 각 서비스별 t_meta_xx 테이블에서 유효기간이 지난 Cache Content 를 삭제 상태로 update 처리
	
	// 처리 대상 시간 구간 계산 (micro-second 단위 )
	long long timeStart;
	long long timeEnd;

	// 현재시간 기준으로 Hot Content Cache 의 보존 만료 시간을 계산.
	timeEnd = timeNow - ( CServiceConfig::GetInstance()->GetCacheFileTimeout() * SEC_TO_MICROSEC );

	// 조회 시작 시간을 계산.
	// - 본 값이 없을 경우... RCDB를 Full Scan 해야 하므로.. RCDB 에 부하 발생 가능.
	// - 따라서 보존 만료 시간 이전 48 시간 정도로 조회 범위를 감소시킨다.
	timeStart = timeNow - ( ( CServiceConfig::GetInstance()->GetCacheFileTimeout() + CACHE_DELETE_RANGE ) * SEC_TO_MICROSEC );

	std::vector< std::string >::const_iterator it;
	for( it = vecService.begin(); it != vecService.end(); ++it )	
	{
		snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
			"UPDATE t_meta_%s SET deleted_yn = 'Y' ,get_lastmodified = %lld "
			"WHERE is_cache = 'Y' AND deleted_yn = 'N' "
			"AND get_lastmodified >= '%lld' "
			"AND get_lastmodified <= '%lld';"
			, it->c_str()
			, timeNow, timeStart, timeEnd
			);

		// Query 실행 
		rcdb.PgDoExec( szQuery );

		// Update 처리 결과 Row 수 확인.
		nResult = rcdb.GetCmdTuples();
		if( nResult > 0 )
		{
			_LOG( LINF, "CCacheDelete: t_meta_%s delete count[%d]", it->c_str(), nResult );
		}

		rcdb.PgClear();
	}

	// 작업이 완료된 경우... RCDB 연결 해제.
	rcdb.PgClear();
	rcdb.PgCloseDB();

	return true;
}
