#include "ClientThread.h"

#include "Logger.h"
#include "ServiceConfig.h"

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>



// 생성자
CClientThread::CClientThread( CStorageStatus * pStorage, CProcessStatus * pProcess, int clientSocket, char * pszClientAddress )
	: m_pStorageStatus( pStorage )
	, m_pProcessStatus( pProcess )
	, m_socketClientDesc( clientSocket )
	, m_client( clientSocket )
	, m_strClientIp( pszClientAddress )	  
{
	// 멤버 변수 초기화
	m_threadHandle = 0;
}

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

	// client 와 연결된 socket 종료 처리
	m_client.Close();
}

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

		return false;
	}
		
	return true;
}

// 스레드 함수
void* CClientThread::threadFunc( void* arg )
{
	CClientThread* pObject = reinterpret_cast<CClientThread *>( arg );
	pthread_detach( pthread_self() );

	// Client Thread Count 1 증가 처리
	pObject->m_pProcessStatus->PlusClientCount();

	// 실제 작업 수행.
	pObject->Execute();

	// Client Thread Count 1 감소 처리
	pObject->m_pProcessStatus->MinusClientCount();
	
	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;


	// 객체 자동 delete 처리.
	delete pObject;	
	return NULL;
}

// client request 요청을 수신하여 이에 따른 job 을 수행
void CClientThread::Execute()
{
	_LOG( LINF, "client thread start [%s]", m_strClientIp.c_str() );

	// Packet Header 정보를 이용한 client 요청 분석
	int nRead;		
	char command;


	while( 1 )
	{
		// socket 연결을 아래 job 등에서 일부로 끊은 경우를 처리하기 위한 코드.
		// - Server socket 이지만.. 통신 관련 오류시.. server 가 socket 을 끊을 수 있다.
		// - 이는 file 전송 등으로 인해 불필요한 data 가 통신상에 남아 있을 수 있기 때문임.
		if( m_client.IsValidSocket() == false )
			break;

		// Header 수신
		nRead = m_client.GetHeaderInfo( command );
		
		if( nRead == 0 )
		{
			// timeout 발생시 - 잠시 대기 후 계속 수신 대기
			sleep( 2 );
			continue;
		}
		else if( nRead < 0 )
		{
			// Socket 접속 종료 또는 오류 발생시
			// - 관련 로그는 해당 모듈에서 다 찍었으므로.. 여기서는 루프 종료 처리.
			break;
		}
		else
		{
			// data 수신시.
			// 정의된 packet 에만 동작.. 그외에는 무시.			
	
			// Client 요청인 경우.
			switch( command )
			{
				case REQUEST_AVAILABLE_SIZE:   // client 로 부터 available size 요청 수신시
					_LOG( LINF, "client[%s] request available size.", m_strClientIp.c_str() );
					ProcessAvailableSize();
					break;

				case REQUEST_FILE_SAVE:		   // client 로 부터 파일 저장 요청 수신시.
					_LOG( LINF, "client[%s] request file save.", m_strClientIp.c_str() );
					ProcessFileSave();
					break;

				// NEW 2015-10-07 huibong
				// - 파일 존재 여부 확인 요청 관련 기능 추가 (#24683)
				case REQUEST_FILE_CHECK:	   // client 로 부터 File 존재 여부 확인 요청 수신시
					_LOG( LINF, "client[%s] request file check.", m_strClientIp.c_str() );
					ProcessFileCheck();
					break;


				default:                      // 미정의 Type 또는 잘못된 reponse 요청인 경우 ... 연결 해제 처리
					_LOG( LINF, "client[%s] request unknown. check request[%02x].", m_strClientIp.c_str(), command );
					m_client.PrintHeaderToLog();
					m_client.Close();
					break;
			}
		}

	} // while (1)
	

	// client 와 연결 종료 처리.
	_LOG( LINF, "client connection closed.[%s]", m_strClientIp.c_str() );
	m_client.Close();
}

// Client 로 부터 available size 요청 수신시 이를 처리하기 위한 함수.
void CClientThread::ProcessAvailableSize()
{
	// REQUEST_AVAILABLE_SIZE 요청인 경우..
	// Data 부분이 따로 존재하지 않으므로...
	// 바로 응답을 전송하면 된다.
	unsigned long long uAvailableSize;
	uAvailableSize = m_pStorageStatus->GetAvailableSize();
	
	if( m_client.SendResultAvailableSizeToClient( uAvailableSize ) == false )
	{
		LOG( LERR, "client[%s] available size result[%llu] send fail."
			 , m_strClientIp.c_str(), uAvailableSize );
	}
	
	return;
}


// client 로 부터 파일 저장 요청 수신시 이를 처리하기 위한 함수.
void CClientThread::ProcessFileSave()
{
	std::string strErrorMessage;

	// REQUEST_FILE_SAVE 요청인 경우...
	// Data 부준이 존재하므로...
	// Data 부분을 수신 처리한다.
	std::string strHostName;
	std::string strProcessName;
	std::string strFileName;
	unsigned long long uFileSize;

	// Data Body 수신 처리
	if( m_client.RecvBodyFileSaveFromClient( strHostName, strProcessName, strFileName, uFileSize ) == false )
	{
		// Body Data 수신 실패시
		LOG( LERR, "client[%s] request file save. but data body receive fail.", m_strClientIp.c_str() );
		m_client.Close();
		return;
	}

	_LOG( LDBG, "client[%s] request file save.[%s][%s][%s][%llu]"
		  , m_strClientIp.c_str(), strHostName.c_str(), strProcessName.c_str(), strFileName.c_str(), uFileSize );

	// 수신된 Data 검증
	// - strHostName, strProcessName 정보는 없을 수 있으나...
	// - strFileName 정보는 꼭 있어야 함.
	if( strFileName.empty() == true )
	{
		strErrorMessage = "file name empty. check file name.";
		LOG( LERR, "client[%s] request file save. but filename empty.", m_strClientIp.c_str() );

		if( m_client.SendResultFileSaveToClient( false, strErrorMessage ) == false )
		{
			LOG( LERR, "client[%s] file save result[error] send fail.", m_strClientIp.c_str() );
		}

		// 잠시 대기 후 세션 종료 처리
		sleep( 1 );
		m_client.Close();
		return;
	}
	

	// 파일 저장을 위한 폴더 생성 처리.
	std::string strFullPathName;
	strFullPathName = CServiceConfig::GetInstance()->GetSaveRootPath();


	// CHG 2015-09-16 huibong
	// - #24436 에 의해 저장 경로 방식 변경
	// - 기존 : hostname - process name - 파일
	// - 변경 : process name - hostname - 파일
	if( strProcessName.empty() == false )
	{
		strFullPathName.append( "/" );
		strFullPathName.append( strProcessName );

		if( MakeDirectory( strFullPathName ) == false )
		{
			strErrorMessage = "process name directory create fail.[" + strFullPathName + "]";
			LOG( LERR, "client[%s] process name directory create fail [%s]", m_strClientIp.c_str(), strFullPathName.c_str() );

			if( m_client.SendResultFileSaveToClient( false, strErrorMessage ) == false )
			{
				LOG( LERR, "client[%s] file save result[error] send fail.", m_strClientIp.c_str() );
			}

			// 잠시 대기 후 세션 종료 처리
			sleep( 1 );
			m_client.Close();
			return;
		}
	}
	
	if( strHostName.empty() == false )
	{
		strFullPathName.append( "/" );
		strFullPathName.append( strHostName );

		if( MakeDirectory( strFullPathName ) == false )
		{
			strErrorMessage = "hostname directory create fail.[" + strFullPathName + "]" ;
			LOG( LERR, "client[%s] hostname directory create fail [%s]", m_strClientIp.c_str(), strFullPathName.c_str() );
			
			if( m_client.SendResultFileSaveToClient( false, strErrorMessage ) == false )
			{
				LOG( LERR, "client[%s] file save result[error] send fail.", m_strClientIp.c_str() );
			}

			// 잠시 대기 후 세션 종료 처리
			sleep( 1 );
			m_client.Close();
			return;
		}
	}


	// 파일 open 후 저장 처리.
	strFullPathName.append( "/" );
	strFullPathName.append( strFileName );
	
	FILE * pFile = NULL;
	pFile = fopen( strFullPathName.c_str(), "w" );
	if( pFile == NULL )	
	{
		// File open 실패시...
		int errorNum = errno;
		char tempBuffer[1024];
		snprintf( tempBuffer, sizeof( tempBuffer ) - 1, "file open failed to write.[%s] [%d][%s]", strFullPathName.c_str(), errorNum, strerror( errorNum ) );
		strErrorMessage.append( tempBuffer );

		LOG( LERR, "%s", strErrorMessage.c_str() );

		// client 로 오류 전송 처리.
		m_client.SendResultFileSaveToClient( false, strErrorMessage );

		// 잠시 대기 후 세션 종료 처리
		sleep( 1 );
		m_client.Close();
		return;
	}
	
	// 파일 수신 및 저장 처리
	if( m_client.ReceiveToFile( pFile, uFileSize, strErrorMessage ) == false )
	{
		// 수신 실패시...
		// - 파일 삭제
		// - socket 종료 처리
		fclose( pFile );

		// client 로 오류 전송 처리.
		m_client.SendResultFileSaveToClient( false, strErrorMessage );

		LOG( LERR, "client[%s] file save fail [%s].", m_strClientIp.c_str(), strErrorMessage.c_str() );

		// 잠시 대기 후 세션 종료 처리
		sleep( 1 );
		unlink( strFullPathName.c_str() );
		m_client.Close();
	}
	else
	{
		// 파일 수신 처리 성공시..
		fclose( pFile );

		_LOG( LINF, "client[%s] file save OK [%s][%llu]", m_strClientIp.c_str(), strFullPathName.c_str(), uFileSize );

		// 성공 처리 응답.
		strErrorMessage.clear();
		if( m_client.SendResultFileSaveToClient( true, strErrorMessage ) == false )
		{
			LOG( LERR, "client[%s] file save result[OK] send fail.", m_strClientIp.c_str() );
		}
	}
		
	return;	
}

// 전달받은 Directory 정보를 확인하여 존재하지 않을 경우 생성처리.
bool CClientThread::MakeDirectory( const std::string& szFullPath )
{
	if( szFullPath.size() <= 0 )
		return false;

	struct stat dirStat;

	// 디렉토리 정보 검사.
	// 해당 이름을 가진 Directory 또는 파일이 존재하고
	if( lstat( szFullPath.c_str(), &dirStat ) == 0 )
	{
		// 해당 정보가 Directory 인 경우
		if( S_ISDIR( dirStat.st_mode ) != 0 )
			return true;
	}

	// 해당 디렉토리가 존재하지 않는 경우 무조건 생성 처리.
	//char tempBuffer[1024];
	//sprintf( tempBuffer, "/bin/mkdir -p -m0755 %s", szFullPath.c_str() );
	//system( tempBuffer );

	// CHG 2014-11-04 huibong
	// - system() 함수 대신 mkdir() function use
	// - 오류 발생 관련 로그 처리 기능 보강
	if( mkdir( szFullPath.c_str(), 0755) == 0 )
    {
		_LOG( LINF, "Directory create OK. [%s]", szFullPath.c_str());
		return true;
	}
	else
	{
		int errorNum = errno;

        LOG( LERR, "Directory create faild. [%s][%d][%s]", szFullPath.c_str(), errorNum, strerror(errorNum));
        return false;
	}
}


// client 로 부터 파일 존재 여부 확인 요청 수신시 이를 처리하기 위한 함수.
void CClientThread::ProcessFileCheck()
{
	std::string strErrorMessage;

	// REQUEST_FILE_CHECK 요청인 경우...
	// Data 부준이 존재하므로...
	// Data 부분을 수신 처리한다.
	std::string strHostName;
	std::string strProcessName;
	std::string strFileName;

	// 로컬 파일의 크기 정보를 저장하기 위한 변수.
	unsigned long long uFileSize = 0;
	
	// Data Body 수신 처리
	if( m_client.RecvBodyFileCheckFromClient( strHostName, strProcessName, strFileName ) == false )
	{
		// Body Data 수신 실패시
		LOG( LERR, "client[%s] request file check. but data body receive fail.", m_strClientIp.c_str() );
		m_client.Close();
		return;
	}

	_LOG( LDBG, "client[%s] request file check.[%s][%s][%s]"
		  , m_strClientIp.c_str(), strHostName.c_str(), strProcessName.c_str(), strFileName.c_str() );

	// 수신된 Data 검증
	// - strHostName, strProcessName 정보는 없을 수 있으나...
	// - strFileName 정보는 꼭 있어야 함.
	if( strFileName.empty() == true )
	{
		strErrorMessage = "file name empty. check file name.";
		LOG( LERR, "client[%s] request file check. but filename empty.", m_strClientIp.c_str() );

		if( m_client.SendResultFileCheckErrorToClient( strErrorMessage ) == false )
		{
			LOG( LERR, "client[%s] file check result[error] send fail.", m_strClientIp.c_str() );
		}

		// 잠시 대기 후 세션 종료 처리
		sleep( 1 );
		m_client.Close();
		return;
	}


	// 물리 파일 존재 여부 검증
	std::string strFullPathName;
	strFullPathName = CServiceConfig::GetInstance()->GetSaveRootPath();
	
	// CHG 2015-09-16 huibong
	// - #24436 에 의해 저장 경로 방식 변경
	// - 기존 : hostname - process name - 파일
	// - 변경 : process name - hostname - 파일
	if( strProcessName.empty() == false )
	{
		strFullPathName.append( "/" );
		strFullPathName.append( strProcessName );		
	}

	if( strHostName.empty() == false )
	{
		strFullPathName.append( "/" );
		strFullPathName.append( strHostName );		
	}
	
	// 파일 최종 경로 조합
	strFullPathName.append( "/" );
	strFullPathName.append( strFileName );

	// 해당 물리 파일 존재 여부 검증.
	// - 존재할 경우... 파일 size 정보 추출
	// - 존재하지 않을 경우.. 파일 size 정보를 0 으로 설정 처리.

	struct stat st;
	if( stat( strFullPathName.c_str(), &st ) == 0 )
	{
		// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
		if( S_ISREG( st.st_mode ) != 0 )
		{
			// size 정보 추출
			uFileSize = st.st_size;
		}
		else
		{
			// 해당 경로명 정보가 파일이 아닌 경우....
			char tempBuffer[1024];
			snprintf( tempBuffer, sizeof( tempBuffer ) - 1, "file check error.[%s][Not file. directory or link.]", strFullPathName.c_str() );
			strErrorMessage.append( tempBuffer );

			LOG( LERR, "client[%s] request file check error.[%s][Not file. directory or link]", m_strClientIp.c_str(), strFullPathName.c_str());

			if( m_client.SendResultFileCheckErrorToClient( strErrorMessage ) == false )
			{
				LOG( LERR, "client[%s] file check result[error] send fail.", m_strClientIp.c_str() );
			}
			return;
		}
	}
	else
	{
		int errorNum = errno;
		if( errorNum == ENOENT )
		{
			// 해당 파일이 존재하지 않는 경우....
			// - size 정보를 0 으로 설정 처리
			uFileSize = 0;
		}
		else
		{
			// 그외 오류 발생시.
			strErrorMessage.clear();

			char tempBuffer[1024];
			snprintf( tempBuffer, sizeof( tempBuffer ) - 1, "file check error.[%s] [%d][%s]", strFullPathName.c_str(), errorNum, strerror( errorNum ) );
			strErrorMessage.append( tempBuffer );
						
			LOG( LERR, "client[%s] request file check error.[%s][%d][%s]", m_strClientIp.c_str(), strFullPathName.c_str(), errorNum, strerror( errorNum ) );
			
			if( m_client.SendResultFileCheckErrorToClient( strErrorMessage ) == false )
			{
				LOG( LERR, "client[%s] file check result[error] send fail.", m_strClientIp.c_str() );
			}

			return;
		}
	}

	// 파일 size 정보가 정상적으로 추출된 경우...
	// 해당 정보를 client 에게 전송 처리한다.
	if( m_client.SendResultFileCheckToClient( uFileSize ) == false )
	{
		LOG( LERR, "client[%s] file check result[size: %llu] send fail.", m_strClientIp.c_str(), uFileSize );		
	}
	
	return;
}
