#include "StorageUsedCalcThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include "RcdbInfo.h"
#include "Database.h"
#include "ReportLog.h"

#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <sys/types.h>
#include <sys/wait.h>


// sqlite SQLITE_BUSY 잦은 발생으로 sqlite 관련 작업 지연 발생 (#21440)
// 특히 timestamp 변경시마다.. Network 통계 수신 처리로 인해 sqlite 지연 작업이 많이 발생하므로.
// 이를 회피하기 위해 스토리지 계산을 아래 설정값 만큼 지연 후 동작하도록 한다.
#define TIMESTAMP_DELAY_SEC            20                       // 5분단위 timestamp 변경시... 변경 후.. 어느만큼 delay 를 허용할지 시간값.

#define SQLITE_DB_LOCK_TIMEOUT       1000                       // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec)


// CHG 2016-05-09 huibong
// - #27514 의 내역에 따라 서비스별 Storage 사용량 report 기록 비율을 90% -> 97% 로 수정한다.
#define REPORT_STORAGE_USED_LIMIT      97                       // Storage 사용량이 해당 값(%)을 초과할 경우.. report 로그에 기록토록 한다.


#define FULL_SCAN_PROCESS_NAME       "/user/service/bin/rc_du"  // Full Scan Process Name...


// Scan Mode
#define SCAN_MODE_SKIP         0
#define SCAN_MODE_PARTIAL      1
#define SCAN_MODE_FULL         2
#define SCAN_MODE_LOCAL_COPY   3    // 증감 scan 은 10분 단위로 수행, storge 사용량 통계는 5분 단위로 추출해야 하므로..
                                    // 본 모드에서는 local db 에 저장된 이전 timestamp 값을 select 하여 현재 timestamp 로 INSERT 처리한다.
									

// 생성자..
CStorageUsedCalcThread::CStorageUsedCalcThread()
{
	m_threadHandle = 0;
}

// 소멸자..
CStorageUsedCalcThread::~CStorageUsedCalcThread()
{
	// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
	if( m_threadHandle != 0 )
		pthread_cancel(m_threadHandle);
}

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

		return false;
	}

	return true;
}


// Thread 함수...
void * CStorageUsedCalcThread::threadFunc( void * arg )
{
	CStorageUsedCalcThread * pObject = reinterpret_cast<CStorageUsedCalcThread *>(arg);
	pthread_detach( pthread_self() );

	// 초기 기동시...
	// 잠시 sleep 했다가.. 작업을 시작한다.
	sleep( 3 );

	// 실제 본 thread function 에서 loop 를 동작하는 것이 아니라....
	// 객체 execute 함수 내에서 loop 로 동작하도록 한다.
	pthread_testcancel();
	pObject->Execute();
	pthread_testcancel();

	// Thread 종료시 Handle 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}


// 서비스별 스토리지 사용량 계산 관련 최종 업무 Flow
// - 2015-08-31 최종 갱신
//
// 업무 Flow
// 1. rc_du 가 스토리지 사용량 계산 관련 Full Scan 을 담당..
//    - full_scan 이 필요할 경우 (30 분 마다 주기적으로 실행) rc_du 를 실행시킨 후.. 
//    - rc_du 가 끝날때까지 대기. ( KT music 4000 만건 depth=1 수준의 경우..  계산시간 약 1분 소요)
//    - rc_du 실행이 완료되면 RCDB t_dav_use_dir 계산결과 및 결과 관련 기준 정보를 조회하여 Local DB 에 저장처리.
// 
// 2. 증감 scan 은 10 분마다 수행
//    - RCDB 서비스 정보와 Local DB 에 저장된 계산관련 기준 정보를 바탕으로 10분마다 계산을 위해
//    - 증가량 : resource_id 값을 기준..
//    - 감소량 : 기존과 같이 get_lastmodified 시간 정보를 기준으로 계산
//    - 계산된 결과값은 GTS 전송 모듈의 처리를 위해 Local DB 에 저장 처리.
//
// 참고사항
// 1. GTS 전송 처리는 다른 Thread 모듈에서 수행하며... 
//    - 추가로 특정 서비스 통계는 전송하지 않는 기능을 구현한다. ( RC 이관 등의 작업시 스토리지 통계 정보가 중복 표시되는 상황을 해결하기 위함)
// 2. rc_du 의 return 값은 전달 인자 오류 발생시에만 오류로 처리된다.
//    - 본 모듈에서는 rc_du 를 인자값 없이 실행하므로 return 값을 가지고 정상 실행 여부를 판단할 수 없으므로.. 반환값에 대한 검사는 수행하지 않는다.
// 3. rc_du 모듈은 연동을 위해 다음과 같이 수정 처리한다. (노경민 과장과 협의 완료)
//    - depth = 0 인 경우 SUM Query 를 수행하여 사용량을 계산.
//    - depth > 0 인 경우 기존과 같이 모든 Content 를 SELECT 하여 자체 계산한 후 해당 사용량을 저장 처리


void CStorageUsedCalcThread::Execute()
{

	time_t currentTime;                   // 현재 시간 정보를 저장하기 위한 변수..	
	unsigned int nowTimestamp;            // 현재 시간의 timestamp 정보를 저장하기 위한 변수.
	unsigned int lastCheckTimestamp = 0;  // 마지막 처리한 timestamp 값을 저장하기 위한 변수.

	// Scan 방법에 대한 정보 저장.
	int nScanMode;      

	// RCDB 접속 정보 저장 관련 객체 생성 및 초기화...
	CRcdbInfo rcdbInfo;
	rcdbInfo.Load();

	// Query 관련 변수 초기화
	char szQuery[2048];

	// Logging
	_LOG( LINF, "StorageUsedCalcThread: start...");


	// 루프를 돌면서....
	while( 1 )
	{

		// 1. 현재 시간 정보를 추출하여 5분단위 timesamp 를 계산
		// - 트래픽 통계는 과거 5분간의 통계 정보를 계산하여 전송하므로.... timestamp 값이 현재 timestamp 값보다 1 이 적음.
		// - 따라서 Storage 통계 역시... timestmap 값이 변경될때 계산하지만.. 과거 5분간의 Data 이므로.. -1 을 해야 함.
				
		// sqlite 경쟁 상황 회피를 위해 계산 시작 시점의 delay 처리 기능 추가 (#21440)
		currentTime = time(0);
		nowTimestamp = ( ( currentTime - TIMESTAMP_DELAY_SEC + 299 ) / 300 ) - 1;
		

		// 2. 스토리지 사용량 계산 방법을 결정.
		//   - 최초 기동시 : Full Scan..
		//   - 매 30분(00분, 30분)마다: Full Scan...
		//   - 매 10분마다 : Partial Scan...
		if( lastCheckTimestamp == 0 )
		{
			_LOG( LINF, "StorageUsedCalcThread: first start with full scan.");
			nScanMode = SCAN_MODE_FULL;
		}
		else if( nowTimestamp != lastCheckTimestamp )
		{			
			// 매시간 0, 30분에 full scan 수행...
			// timestamp 는 5분단위 값이므로... 1시간은 12 timestamp 로 계산됨.
			// 따라서 매시간 0분, 30분은 timestamp % 12 == 1, 7  이지만...
			// timestamp 계산시 -1 을 수행하므로.. 매 시간 00분, 30분은 timestamp % 12 == 0, 6 임.
			if( (nowTimestamp % 12) == 0 || (nowTimestamp % 12) == 6 )
			{
				_LOG( LINF, "StorageUsedCalcThread: 00, 30 min full scan set.");
				nScanMode = SCAN_MODE_FULL;
			}
			else if( (nowTimestamp % 2) == 0 )
			{
				// 만약 Only Full Scan 만 사용토록 설정되어 있는 경우....
				if( CProcessConfig::GetInstance()->IsStorageOnlyFullScanUse() == true )
				{
					_LOG( LINF, "StorageUsedCalcThread: only full scan set.");
					nScanMode = SCAN_MODE_FULL;
				}
				else
				{
					// 10 분 단위 partial scan 임.
					_LOG( LINF, "StorageUsedCalcThread: partial scan set per 10 min.");
					nScanMode = SCAN_MODE_PARTIAL;
				}
			}
			else
			{
				// full scan, 증감 scan 이 아닌 경우..  5분 단위 Data 처리를 위해 Local DB 이전 timestamp 의 값을 select -> insert 처리한다.
				nScanMode = SCAN_MODE_LOCAL_COPY;
			}
		}
		else
		{
			// 최초 기동이 아니고...  동일한 timestamp 시간인 경우.... SKIP 처리.
			nScanMode = SCAN_MODE_SKIP;
		}


		// 3. Scan 모드에 따른 동작 처리..
		if( nScanMode == SCAN_MODE_FULL )
		{
			_LOG( LINF, "StorageUsedCalcThread: full scan start.");

			// 조회 결과 저장 멤버 변수 초기화.
			m_vecStorage.clear();

			// Full Scan mode 일 경우....
			// - rc_du 를 동작시킨다. ( 만약 rc_du 동작 실패시.. 증감 scan 을 수행한다.)
			// - rc_du 동작이 완료되면.... 해당 meta 테이블에서 MAX resource_id, get_lastmodified 정보를 추출한다.
			// - t_dav_use_dir 테이블의 조회 값과... resource_id, get_lastmodified 값을 Local DB 에 저장한다.
			if( ExecuteFullScanProcess() == false )
			{
				_LOG( LWAR, "StorageUsedCalcThread: full scan failed. use partical scan.");

				// full scan process 수행 실패시... 증감 scan 을 수행한다.
				nScanMode = SCAN_MODE_PARTIAL;
			}
			else
			{
				// full scan 외부 모듈 수행이 정상적으로 처리된 경우...RCDB 에 접속하여.. 해당 결과를 가져온다.
				if( GetFullScanResult( rcdbInfo, nowTimestamp ) == false )
				{
					_LOG( LWAR, "StorageUsedCalcThread: RCDB result get failed. full scan failed..");
				}
				else
				{
					// full scan 결과를 RCDB 로 부터 정상적으로 가져온 경우... Local DB 에 해당 결과를 저장처리한다.
					if( SaveStorageStatToLocal( nowTimestamp ) == true )
					{
						_LOG( LINF, "StorageUsedCalcThread: full scan complete.");
					}
					else
					{
						_LOG( LWAR, "StorageUsedCalcThread: local db save failed. full scan failed..");
					}

					// 추가 기능...
					// - Storage 사용량이 지정된 값 REPORT_STORAGE_USED_LIMIT 을 초과할 경우... report 로그에 기록하도록 한다.
					CheckQuotaForReport( nowTimestamp );

				} // RCDB 에서 full scan 조회 결과를 가져오는데 성공한 경우..

			} // Full Scan 외부 모듈 실행 성공시..

		} // Full Scan 모드 인 경우.



		// 4. 증감 Scan 을 수행한다.
		if( nScanMode == SCAN_MODE_PARTIAL )
		{
			_LOG( LINF, "StorageUsedCalcThread: partial scan start.");

			// 조회 결과 저장 멤버 변수 초기화.
			m_vecStorage.clear();

			// RCDB 와 연결을 수행한다.
			DataBase rcdb;
			if( rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw) == NULL )
			{
				// 연결 실패시...
				_LOG( LERR, "StorageUsedCalcThread: RCDB connection failed. partial scan failed..");
			}
			else
			{
				// RCDB 연결이 성공인 경우...

				// RCDB t_sms_sp_svc_product 테이블에서 서비스 정보 추출.
				if( GetServiceInfoFromRCDB( rcdb ) <= 0 )
				{
					_LOG( LERR, "StorageUsedCalcThread: Service info select failed. partial scan failed..");
				}
				else
				{
					// Local DB 에 저장된 각 서비스별 Storage 마지막 사용량 정보를 가져온다.
					if( GetLastStorageStatFromLocal() <= 0 )
					{
						_LOG( LERR, "StorageUsedCalcThread: Local db select failed. partial scan failed..");
					}
					else
					{
						// 각 서비스별 Storage 사용량을 계산한다.
						unsigned long long max_resource_id = 0;
						unsigned long long max_get_lastmodified = 0;

						// 3.4 버전 적용에 따라.. t_meta_xxxx 로 서비스별 meta table 이 분리됨.
						// - 3.4 이전 t_dav_resource 통 table 사용 관련해서는 지원하지 않음. ( 3.5 버전 협의 내역 )

						// 서비스 단위로 사용량을 계산
						std::vector<struct storage_used_info>::iterator it;
						for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
						{
							// 각 서비스별로 RCDB 의 Storage 사용량 정보를 계산을 위해..
							// t_meta_XXX 테이블에서 MAX resource_id, get_lastmodified 정보를 추출한다.
							snprintf( szQuery, 2047,
									  "SELECT COALESCE( MAX(resource_id), 0 ), COALESCE( MAX(get_lastmodified), 0 ) FROM t_meta_%d "
									  , it->svc_seq );

							rcdb.PgDoExec( szQuery );
							if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
							{
								// Query 수행 결과 오류 발생시...
								// - 해당 서비스에 대한 처리는 더 이상 무의미 하므로 다음 서비스 정보를 조회
								LOG( LERR, "StorageUsedCalcThread: RCDB SELECT max value query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

								rcdb.PgClear();
								continue;
							}
							else
							{
								// Query 정상 수행시.. 조회된 Max 값을 저장 처리.
								max_resource_id = atoll( rcdb.GetValue( 0, 0 ) );
								max_get_lastmodified = atoll( rcdb.GetValue( 0, 1 ) );
							}

							// RCDB 조회 결과 set 를 clear 처리한다.
							rcdb.PgClear();

							// t_meta_XXX 테이블에서 스토리지 증감량을 계산한다.
							// - RCDB 조회 결과가 (-)일 경우 GREATEST() 함수를 사용하여 방어토록 기능 추가 (#21121)

							snprintf( szQuery, 2047,
									  "SELECT GREATEST( STORAGE_ADD.add_size - STORAGE_DEL.del_size + %llu, 0 ) "
									  "FROM "
									  "  ( SELECT COALESCE(SUM(get_content_length), 0 ) as add_size FROM t_meta_%d "
									  "     WHERE resource_id > %llu AND resource_id <= %llu "
									  "       AND sp_user_seq = %d AND sp_svc_tran_id = %d AND resource_type = 0 AND deleted_yn = 'N' AND is_cache = 'N' "
									  "  ) STORAGE_ADD, "
									  "  ( SELECT COALESCE(SUM(get_content_length), 0 ) as del_size FROM t_meta_%d "
									  "     WHERE get_lastmodified > %llu AND get_lastmodified <= %llu "
									  "      AND creation_date <= %llu "
									  "      AND sp_user_seq = %d AND sp_svc_tran_id = %d AND resource_type = 0 AND deleted_yn = 'Y' AND is_cache = 'N' "
									  "  ) STORAGE_DEL "
									  , it->used
									  , it->svc_seq
									  , it->check_resource_id, max_resource_id, it->user_seq, it->svc_seq
									  , it->svc_seq
									  , it->check_get_lastmodified, max_get_lastmodified, it->check_get_lastmodified, it->user_seq, it->svc_seq
									  );

							_LOG( LINF, "StorageUsedCalcThread: partial scan query start. timestamp[%u] svc[%d][%d] old_used[%llu] resource_id[%llu, %llu] get_lastmodified[%llu, %llu]"
								  , it->timestamp
								  , it->user_seq, it->svc_seq
								  , it->used
								  , it->check_resource_id, max_resource_id
								  , it->check_get_lastmodified, max_get_lastmodified );

							//_LOG( LDBG, "StorageUsedCalcThread: partial scan query[%s]", szQuery );

							rcdb.PgDoExec( szQuery );
							if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
							{
								// Query 수행 결과 오류 발생시...
								LOG( LERR, "StorageUsedCalcThread: partial scan RCDB scan failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );
							}
							else
							{
								// Query 수행이 정상인 경우. 1개 Row 만 존재하므로..
								it->used = atoll( rcdb.GetValue( 0, 0 ) );   // storage used

								// 나머지 다른 값도 설정 처리한다.
								it->timestamp = nowTimestamp;
								it->check_resource_id = max_resource_id;
								it->check_get_lastmodified = max_get_lastmodified;

								_LOG( LINF, "StorageUsedCalcThread: partial scan result. timestamp[%u] svc[%d][%d] quota[%llu] used[%llu]"
									  , it->timestamp, it->user_seq, it->svc_seq, it->quota, it->used );

							}

							// 다음 Query loop 조회를 위해 RCDB 조회 결과 set 를 clear 처리한다.
							rcdb.PgClear();

						}


						// 최종 서비스 및 Storage 사용량 정보를 RCDB t_dav_use_dir 테이블에 업데이트 처리한다.
						// - 업데이트 처리시... RCDB 조회 관련 오류가 발생했을 수 있으므로... it->timestamp = nowTimestamp 정보만 업데이트 처리한다.
						SaveStorageStatToRCDB( rcdb, nowTimestamp );

						// 최종 서비스 및 Storage 사용량 정보를 Local DB 에 저장 처리.
						if( SaveStorageStatToLocal( nowTimestamp ) == true )
						{
							_LOG( LINF, "StorageUsedCalcThread: partial scan complete." );
						}
						else
						{
							_LOG( LWAR, "StorageUsedCalcThread: local db save failed. partial scan failed.." );
						}

						// 추가 기능...
						// - Storage 사용량이 지정된 값 REPORT_STORAGE_USED_LIMIT 을 초과할 경우... report 로그에 기록하도록 한다.
						CheckQuotaForReport( nowTimestamp );

					}
				}
			}

			// RCDB 접속 해제 
			rcdb.PgClear();
			rcdb.PgCloseDB();

			_LOG( LINF, "StorageUsedCalcThread: partial scan end.");
		}


		// 5. Local copy job 을 수행한다.
		if( nScanMode == SCAN_MODE_LOCAL_COPY )
		{
			// Local DB 관련 변수 초기화.
			sqlite3 * db = NULL;
			int db_result;
			char * db_err;
			int nRetryCount;    // Local DB 작업 재시도 count

			// Local DB 에 저장된 이전 timestamp 계산값을 SELECT 하여 현재 Timestamp 값으로 INSERT 처리한다.
			_LOG( LINF, "StorageUsedCalcThread: local copy start.");

			// Local DB 에 접속..
			db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
			if( db_result != SQLITE_OK )
			{
				LOG( LERR, "StorageUsedCalcThread: local copy job open sqlite3 failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db));
				sqlite3_close(db);
				db = NULL;
			}
			else
			{
				// SELECT -> INSERT QUERY 조립
				snprintf( szQuery, 2047,
						"INSERT INTO storage_stat( timestamp, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, insert_time, send_flag) "
						"SELECT %u, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, datetime('now', 'localtime'), 0 "
						"FROM storage_stat "
						"WHERE timestamp = (%u - 1) "
						, nowTimestamp, nowTimestamp );

				// sqlite 에.. Query 수행.
				db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
				nRetryCount = 0;

				// SQLITE_BUSY 상황시.. 60회 재시도
				while( db_result == SQLITE_BUSY  && nRetryCount < 60 )
				{
					sqlite3_free(db_err);
					sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
					++nRetryCount;

					LOG( LWAR, "StorageUsedCalcThread: local copy sqlite3_exec SQLITE_BUSY, Retry[%d]", nRetryCount );

					// 재시도
					db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);	
				}

				if( db_result != SQLITE_OK )
				{
					_LOG( LERR, "StorageUsedCalcThread: local copy db insert failed. [%u][%s]", nowTimestamp, db_err );
					sqlite3_free(db_err);
				}
				else
				{
					_LOG( LINF, "StorageUsedCalcThread: local copy end. [%d]row insert.", sqlite3_changes(db) );
				}

				// Local DB 연결 해제.
				sqlite3_close(db);
				db = NULL;
			}
		}
		

		// 6. 작업 완료 후 잠깐 sleep 처리..
		lastCheckTimestamp = nowTimestamp;
		sleep(2);
	}

	return;
}


// Full Scan 을 담당하는 rc_du 를 실행시킨다.
bool CStorageUsedCalcThread::ExecuteFullScanProcess()
{
	// rc_du 가 존재하는지 검사한다.
	if( access( FULL_SCAN_PROCESS_NAME, X_OK ) != 0 )
	{
		LOG( LERR, "StorageUsedCalcThread::ExecuteFullScanProcess: full scan process[%s] not exist.", FULL_SCAN_PROCESS_NAME );
		return false;
	}

	// rc_du 가 존재하는 경우..... system 명령을 이용하여 동작시킨다.
	int result = system( FULL_SCAN_PROCESS_NAME );
	int errorNum = errno;
		
	// rc_du 가 정상적으로 동작해도.. system() 함수의 반환값이 -1 인 경우가 존재함....
	// 따라서... 이미 위에서 해당 파일이 존재하는지 검사했으므로..... system() 함수 실행 관련 반환값은 그냥 로깅 목적으로 표시만 하도록 수정한다.
	if( result == -1 ) // system 명령에서 -1 을 반환... 내부 오류인 경우도 있고.. 정상 수행되었어도.. -1 을 반환하는 경우가 있음.
	{
		LOG( LWAR, "StorageUsedCalcThread::ExecuteFullScanProcess: full scan process[%s] system call return error. but ignore.[%d][%d][%s]"
				, FULL_SCAN_PROCESS_NAME, result, errorNum, strerror(errorNum));
	}
	else
	{
		// rc_du 는 인자값 오류시에는 오류코드를 반환.. 그 외에는 모두 정상 동작으로 처리되므로..
		// RCDB 접속 연결 실패 등과 같은 문제를 반환값을 통해 알 수는 없다.
		// 따라서... 일단 수행되면.. 정상 수행 된것으로 판단하고.. 반환값에 대한 확인은 하지 않는다.
		_LOG( LINF, "StorageUsedCalcThread: full scan process exit. return code[%d]", WEXITSTATUS(result));
	}

	return true;
}



// RCDB 에 저장된 full scan 실행 결과 정보를 조회하여 내부 객체인 m_vecStorage 에 저장 처리한다.
bool CStorageUsedCalcThread::GetFullScanResult( CRcdbInfo & rcdbInfo, unsigned int nowTimestamp )
{
	DataBase rcdb;
	char szQuery[1024];
	int nResult;

	// meta 테이블에서 MAX resource_id, get_lastmodified 정보 추출값 저장.
	unsigned long long max_resource_id      = 0;
	unsigned long long max_get_lastmodified = 0;

	// DB 조회 정보를 저장하기 위한 임시 변수.
	struct storage_used_info stStorage;

	// 조회 정보를 저장하기 위한 멤버 변수 초기화.
	m_vecStorage.clear();
		
	// 1. RCDB 에 접속한다.
	// - RCDB 와 연결은 필요시마다 연결 후 해제 처리한다. (세션 유지 않도록...)
	if( rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw) == NULL )
	{
		// 연결 실패시...
		LOG( LERR, "StorageUsedCalcThread::GetFullScanResult: RCDB connection failed.");
		return false;
	}
	
	// 3.4 버전 적용에 따라.. t_meta_xxxx 로 서비스별 meta table 이 분리됨.
	// - 3.4 이전 t_dav_resource 통 table 사용 관련해서는 지원하지 않음. ( 3.5 버전 협의 내역 )

	// 각 서비스별 사용량 조회 후.. 각 서비스별 max resource_id, get_lastmodified  정보 추출.
	
	
	// 2. t_dav_use_dir 테이블에서... 서비스별 스토리지 사용량 정보 추출..						
	snprintf( szQuery, 1023,
		"SELECT A.sp_user_seq, A.sp_svc_tran_id, B.max_str_size, A.dir_used "
		"FROM t_dav_use_dir A, t_sms_sp_svc_product B "
		"WHERE A.depth = 0 "
		"  AND A.sp_user_seq    = B.sp_user_seq "
		"  AND A.sp_svc_tran_id = B.sp_svc_tran_id " );

	rcdb.PgDoExec( szQuery );
	if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
	{
		// Query 수행 결과 오류 발생시...
		LOG( LERR, "StorageUsedCalcThread::GetFullScanResult: RCDB t_dav_use_dir select query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

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

		return false;
	}

	// Query 수행이 정상인 경우.
	nResult = rcdb.GetNoTuples();
	for( int i = 0; i < nResult; i++ )
	{
		stStorage.user_seq = atoi( rcdb.GetValue( i, 0 ) );   // user_seq
		stStorage.svc_seq  = atoi( rcdb.GetValue( i, 1 ) );   // svc_seq
		stStorage.quota    = atoll( rcdb.GetValue( i, 2 ) );  // storage quota
		stStorage.used     = atoll( rcdb.GetValue( i, 3 ) );  // storage used

		// 나머지 정보 설정.
		stStorage.timestamp = nowTimestamp;
		stStorage.check_resource_id = 0;
		stStorage.check_get_lastmodified = 0;

		m_vecStorage.push_back( stStorage );
	}

	// RCDB 조회 결과 set 를 clear 처리한다.
	rcdb.PgClear();

	// 3.각각 서비스 table 에 대해 MAX resource_id, get_lastmodified 정보를 추출
	if( m_vecStorage.empty() == false )
	{
		std::vector<struct storage_used_info>::iterator it;
		for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
		{
			// t_meta_xxx 에서 MAX resource_id, get_lastmodified 정보 조회
			snprintf( szQuery, 1023,
				"SELECT COALESCE( MAX(resource_id), 0 ), COALESCE( MAX(get_lastmodified), 0 ) FROM t_meta_%d ", it->svc_seq );

			rcdb.PgDoExec( szQuery );
			if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
			{
				// Query 수행 결과 오류 발생시...
				LOG( LERR, "StorageUsedCalcThread::GetFullScanResult: RCDB SELECT max value query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

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

				return false;
			}

			// Query 정상 수행시.. 조회된 Max 값을 저장 처리.
			max_resource_id      = atoll( rcdb.GetValue( 0, 0 ) );
			max_get_lastmodified = atoll( rcdb.GetValue( 0, 1 ) );

			it->check_resource_id      = max_resource_id;
			it->check_get_lastmodified = max_get_lastmodified;
				
			// RCDB 조회 결과 set 를 clear 처리한다.
			rcdb.PgClear();
		}
	}
	

	// RCDB 접속 해제 처리
	rcdb.PgCloseDB();
	

	// 4. 만약 조회된 결과가 존재하지 않을 경우... false 를 반환한다.
	if( m_vecStorage.empty() == true )
	{
		_LOG( LWAR, "StorageUsedCalcThread::GetFullScanResult: RCDB t_dav_use_dir select query result 0 rows.[%s]", szQuery );
		return false;
	}

	return true;
}


// RCDB t_sms_sp_svc_product 테이블로 부터 서비스 정보를 추출하여.. 멤버변수 m_vecStorage 에 저장한다.
// return value : -1  : RCDB Query 수행 등의 오류 발생시
//                 0  : 처리할 서비스 정보가 존재하지 않는 경우.
//               양수 : 처리할 서비스의 갯수
int CStorageUsedCalcThread::GetServiceInfoFromRCDB( DataBase & rcdb )
{
	// 변수 초기화
	char szQuery[1024];
	int nResult;
	
	// DB 조회 정보를 저장하기 위한 임시 변수.
	struct storage_used_info stStorage;

	// RCDB 조회 정보를 저장하기 위한 멤버 변수 초기화.
	m_vecStorage.clear();

	// RCDB t_sms_sp_svc_product 테이블에서 서비스 정보 추출
	snprintf(szQuery, 1023,
			"SELECT sp_user_seq, sp_svc_tran_id, max_str_size FROM t_sms_sp_svc_product ORDER BY sp_svc_tran_id ASC");

	rcdb.PgDoExec(szQuery);
	if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
	{
		// Query 수행 결과 오류 발생시...
		LOG( LERR, "StorageUsedCalcThread::GetServiceInfoFromRCDB: RCDB t_sms_sp_svc_product select query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

		rcdb.PgClear();
		return -1;
	}

	// Query 수행이 정상인 경우.
	nResult = rcdb.GetNoTuples();

	for( int i = 0; i < nResult; i++ )
	{
		stStorage.user_seq = atoi ( rcdb.GetValue( i, 0 ) );   // user_seq
		stStorage.svc_seq  = atoi ( rcdb.GetValue( i, 1 ) );   // svc_seq
		stStorage.quota    = atoll( rcdb.GetValue( i, 2 ) );   // storage quota

		// 나머지 값은 default 0 으로 설정.
		stStorage.used                   = 0;
		stStorage.check_resource_id      = 0;
		stStorage.check_get_lastmodified = 0;

		m_vecStorage.push_back( stStorage );
	}

	// RCDB 조회 결과 set 를 clear 처리한다.
	rcdb.PgClear();

	// 만약 조회된 결과가 존재하지 않을 경우...0 를 반환한다.
	if( m_vecStorage.empty() == true )
	{
		LOG( LWAR, "StorageUsedCalcThread::GetServiceInfoFromRCDB: RCDB t_sms_sp_svc_product select query result 0 rows.[%s]", szQuery );
		return 0;
	}

	return m_vecStorage.size();
}


// 멤버 변수 m_vecStorage 에 저장된 서비스에 대하여....
// Local db storage_stat 테이블에 저장된 마지막 스토리지 사용량, partial scan 관련 정보 값을 조회하여 저장 처리한다.
// return value :  -1 : Local DB 접속 실패 등의 오류 발생시...
//                 0  : 처리할 서비스 정보가 존재하지 않는 경우.
//               양수 : 처리할 서비스의 갯수
int CStorageUsedCalcThread::GetLastStorageStatFromLocal()
{
	// sqlite 관련 변수 초기화
	sqlite3 * db = NULL;
	sqlite3_stmt * stmt = NULL;
	int db_result;

	const char * select_sql = "SELECT used, check_resource_id, check_get_lastmodified FROM storage_stat "
		                      "WHERE user_seq=? AND svc_seq=? AND timestamp= (select max(timestamp) from storage_stat) ";

	int nRetryCount = 0;

	// sqlite 접속	
	db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
	if( db_result != SQLITE_OK )
	{
		LOG( LERR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db));
		sqlite3_close(db);
		db = NULL;

		return -1;
	}
	
	// Query Prepare 처리
	db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );

	// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
	while( db_result == SQLITE_BUSY  && nRetryCount < 30 )
	{
		// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
		sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
		++nRetryCount;

		LOG( LWAR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: sqlite3 prepare SQLITE_BUSY. Retry[%d]", nRetryCount );

		db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );
	}
	
	if( db_result != SQLITE_OK )
	{
		LOG( LERR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
		sqlite3_close(db);
		db = NULL;
		stmt = NULL;

		return -1;
	}

	// sqlite 접속이 정상적으로 되었으므로..
	// RCDB 정보 기준으로... Local DB 에 저장된 이전 timestamp 의 계산 정보를 추출하여 vector 에 저장한다.
	std::vector<struct storage_used_info>::iterator it;

	for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
	{
		sqlite3_bind_int  ( stmt,  1, it->user_seq );
		sqlite3_bind_int  ( stmt,  2, it->svc_seq );

		// Query 수행
		nRetryCount = 0;
		db_result = sqlite3_step(stmt);

		// 만약 BUSY 상태로 인해 오류가 발생하면..30번 재시도.
		while( db_result == SQLITE_BUSY  && nRetryCount < 60 )
		{
			sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
			++nRetryCount;

			LOG( LWAR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: sqlite3 step SQLITE_BUSY. Retry [%d]", nRetryCount );

			// Query 재수행
			db_result = sqlite3_step(stmt);
		}

		// select 조회 결과가 존재하는 경우... SQLITE_ROW (100 ) 을 반환...
		// select 조회 결과가 없는 경우.. SQLITE_DONE (101) 을 반환.

		// select query 수행시 오류가 발생한 경우...
		if( db_result != SQLITE_ROW && db_result != SQLITE_DONE )
		{
			LOG( LERR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: sqlite3 step error. [%d][%s]", db_result, sqlite3_errmsg(db));

			// 멤버변수의 값을 전체 초기화 하고.. 조회 loop 를 종료한다.
			m_vecStorage.clear();
			break;
		}
		else
		{
			// select 조회 결과가 존재하는 경우... 1개 Row 만 존재하므로...
			if( db_result == SQLITE_ROW )
			{
				it->used                   = sqlite3_column_int64(stmt, 0);
				it->check_resource_id      = sqlite3_column_int64(stmt, 1);
				it->check_get_lastmodified = sqlite3_column_int64(stmt, 2);
			}
		}

		// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
		sqlite3_reset(stmt);
	}

	// sqlite 최종 정리 작업.
	sqlite3_finalize(stmt);
	stmt = NULL;
	sqlite3_close(db);
	db = NULL;

	// 마지막으로.. 멤버변수 m_vecStorage 의 값을 기준으로 Local db 조회 처리가 정상적으로 수행되었는지 확인.
	// - 비정상 조회인 경우 m_vecStorage 갯수가 0 임.
	if( m_vecStorage.empty() == true )
	{
		LOG( LERR, "StorageUsedCalcThread::GetLastStorageStatFromLocal: local db storage_stat get failed." );
		return 0;
	}

	return m_vecStorage.size();
}


// 멤버 변수 m_vecStorage 에 저장된 서비스별 Storage 사용량 정보를 RCDB 에 저장 처리한다.
bool CStorageUsedCalcThread::SaveStorageStatToRCDB( DataBase & rcdb, unsigned int nowTimestamp )
{
	// Query 관련 변수 초기화
	char szQuery[1024];

	// 최종 서비스 및 Storage 사용량 정보를 RCDB t_dav_use_dir 테이블에 업데이트 처리한다.
	// - 업데이트 처리시... RCDB 조회 관련 오류가 발생했을 수 있으므로... it->timestamp = nowTimestamp 정보만 업데이트 처리한다.
	std::vector<struct storage_used_info>::iterator it;
	for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
	{
		if( it->timestamp == nowTimestamp )
		{
			snprintf( szQuery, 1023,
				"UPDATE t_dav_use_dir "
				"SET dir_used = %llu "
				"WHERE depth = 0 AND sp_user_seq = %d AND sp_svc_tran_id = %d"
				, it->used
				, it->user_seq, it->svc_seq );

			rcdb.PgDoExec( szQuery );

			// 적용된 Row 수가 1 row 가 아닌 경우
			if( rcdb.GetCmdTuples() != 1 )
			{
				LOG( LWAR, "StorageUsedCalcThread: t_dav_use_dir update failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );
			}
			else
			{
				_LOG( LDBG, "StorageUsedCalcThread: t_dav_use_dir update ok.!! [%u][%d][%d][%llu]", it->timestamp, it->user_seq, it->svc_seq, it->used );
			}

			rcdb.PgClear();
		}
		else
		{
			_LOG( LWAR, "StorageUsedCalcThread: t_dav_use_dir update skip that rcdb scan faild. timestamp[%u] svc[%d][%d]"
				, it->timestamp, it->user_seq, it->svc_seq );
		}
	}

	rcdb.PgClear();
	return true;
}


// 멤버 변수 m_vecStorage 에 저장된 서비스별 Storage 사용량 정보를 Local DB 에 저장 처리한다.
bool CStorageUsedCalcThread::SaveStorageStatToLocal( unsigned int nowTimestamp )
{
	// sqlite 관련 변수 초기화
	sqlite3 * db = NULL;
	int db_result;
	char * db_err;

	char szQuery[2048];
	int nRetryCount = 0;

	// sqlite 접속	
	db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
	if( db_result != SQLITE_OK )
	{
		LOG( LERR, "StorageUsedCalcThread::SaveStorageStatToLocal: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
		sqlite3_close(db);
		db = NULL;

		return false;
	}

	_LOG( LINF, "StorageUsedCalcThread: Storage scan result save to local db job start. service count [%lu].", m_vecStorage.size());

	// 루프를 돌면서... 서비스별  조회 결과를 sqlite 에 저장 처리.
	std::vector<struct storage_used_info>::iterator it;
	for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
	{
		// 만약 멤버 변수 m_vecStorage 에 저장된 각 timestamp 값이 현재 전달받은 timestamp 값과 동일하지 않을 경우
		// Query 조회 실패 또는 계산 실패 등이 발생한 값이므로.. Local 상에 저장하지 않는다.
		if( it->timestamp == nowTimestamp )
		{
			// INSERT QUERY 조립
			snprintf( szQuery, 2047,
					"INSERT INTO storage_stat( timestamp, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, insert_time, send_flag) "
					"VALUES ( %u, %d, %d, %llu, %llu, %llu, %llu, datetime('now', 'localtime'), 0 )"
					, it->timestamp, it->user_seq, it->svc_seq
					, it->quota, it->used
					, it->check_resource_id, it->check_get_lastmodified );

			// sqlite 에.. INSERT 처리..
			db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
			nRetryCount = 0;

			// 만약 BUSY 상태로 인해 오류가 발생하면.. 중요 통계 Data 이므로.. 60회 정도 재시도.. 후 안되면.. 그냥 저장하지 않고 다음으로 넘어간다.
			while( db_result == SQLITE_BUSY  && nRetryCount < 60 )
			{
				sqlite3_free(db_err);
				sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
				++nRetryCount;

				_LOG( LWAR, "StorageUsedCalcThread::SaveStorageStatToLocal: local db insert SQLITE_BUSY. Retry[%d]", nRetryCount );

				db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
			}

			// INSERT Query 가 실패한 경우....
			//  - 저장된 정보가 아직 GTS 로 전송되지 않은 상태일 수 있으므로.. update 를 시도한다.
			if( db_result != SQLITE_OK )
			{
				_LOG( LWAR, "StorageUsedCalcThread::SaveStorageStatToLocal: local insert failed. update try. [%u][%d][%d][%s]", it->timestamp, it->user_seq, it->svc_seq, db_err );
				sqlite3_free(db_err);

				// 기존 정보가 존재하는 경우 Primary Key 충돌로 INSERT 실패가 발생.. 따라서 UPDATE 처리를 수행한다.
				snprintf( szQuery, 2047, 
						"UPDATE storage_stat "
						"SET quota=%llu, used=%llu, check_resource_id=%llu, check_get_lastmodified=%llu, insert_time=datetime('now', 'localtime')"
						"WHERE timestamp=%u AND user_seq=%d AND svc_seq=%d "
						, it->quota, it->used
						, it->check_resource_id, it->check_get_lastmodified
						, it->timestamp, it->user_seq, it->svc_seq );

				// sqlite 에.. UPDATE 처리..
				db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
				nRetryCount = 0;

				while( db_result == SQLITE_BUSY  && nRetryCount < 30 )
				{
					sqlite3_free(db_err);
					sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
					++nRetryCount;

					_LOG( LWAR, "StorageUsedCalcThread::SaveStorageStatToLocal: local update SQLITE_BUSY. Retry[%d]", nRetryCount );

					db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
				}

				if( db_result != SQLITE_OK )
				{
					LOG( LERR, "StorageUsedCalcThread::SaveStorageStatToLocal: local db save failed.[%u] [%d][%d] [%llu] [%s]", it->timestamp, it->user_seq, it->svc_seq, it->used, db_err );
					sqlite3_free( db_err );
				}
			}
		}
		else
		{
			_LOG( LWAR, "StorageUsedCalcThread::SaveStorageStatToLocal: local db save skip. timestamp different. svc[%d][%d] now[%u] calc_timestamp[%u] "
					, it->user_seq, it->svc_seq, nowTimestamp, it->timestamp );
		}
	}

	// Local DB connection close..
	sqlite3_close(db);
	db = NULL;

	_LOG( LINF, "StorageUsedCalcThread: Storage scan result save to local db job end.");

	return true;
}


// 멤버 객체 m_vecStorage 에 저장된 서비스별 스트리지 사용량을 확인하여.. 지정된 임계치가 초과할 경우. report 로그에 기록 처리한다.
void CStorageUsedCalcThread::CheckQuotaForReport( unsigned int nowTimestamp )
{
	// Storage 사용량이 지정된 값 REPORT_STORAGE_USED_LIMIT 을 초과할 경우... report 로그에 기록하도록 한다.
	std::vector<struct storage_used_info>::iterator it;
	for( it = m_vecStorage.begin(); it != m_vecStorage.end(); ++it )
	{
		// timestamp 가 동일하지 않은 경우는 무시 처리.. ( 잘못된 값일 수 있음 )
		if( it->timestamp != nowTimestamp )
			continue;

		if( it->quota > 0 && ( it->used * 100 / it->quota ) > REPORT_STORAGE_USED_LIMIT )
		{
			_LOG( LWAR, "StorageUsedCalcThread::CheckQuotaForReport: service storage used %d %% over. svc[%d][%d] quota[%llu] used[%llu] percent[%llu %%]"
					, REPORT_STORAGE_USED_LIMIT
					, it->user_seq, it->svc_seq
					, it->quota, it->used
					, it->used * 100 / it->quota );

			// Report 로그에 해당 내역 기록 처리.
			CReportLog reportLog;
			std::string strErrorMessage;
			strErrorMessage.clear();

			if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
			{
				reportLog.GetLastError( strErrorMessage );
				LOG( LERR, "StorageUsedCalcThread::CheckQuotaForReport: report log init failed.[%s]", strErrorMessage.c_str() );
			}
			else
			{
				reportLog.Write( "WAR", "[%s][service storage used percent %d %% over. svc[%d][%d] quota[%llu] used[%llu] percent[%llu %%]"
						, PROG_NAME, REPORT_STORAGE_USED_LIMIT
						, it->user_seq, it->svc_seq
						, it->quota, it->used
						, it->used * 100 / it->quota );
			}
		}
	} 
}

