/***************************************************************************
                LogFileCleaner.cpp
                -----------------------------------------
    begin      : 2011/11/09
    copyright  : (C) 2005 SolutionBox Inc.
    author     : Service 1 Team
    email      : svc1@solbox.com

    CopyRight(C) 2005 SolutionBox Inc. All Rights reserved.
    Redistribution and use in source and binary forms, with or with out
    modification, are not permitted in outside of SolutionBox Inc.
***************************************************************************/
#include "LogFileSender.h"
#include "DaemonConfigs.h"
#include "CcCollectdClientSocket.h"
#include "Logger.h"
#include "String.h"
#include <dirent.h>
#include <sys/stat.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SECONDS_CORRESPOND_TO_ONE_DAY  86400

//#define __HJ_TEST__

#define	ACCESS_LOG_PRE_NAME	"access_log_"
#define	ERROR_LOG_PRE_NAME	"error_log_"

using namespace std;

CLogFileSender::CLogFileSender() : CLogFileProcessThread( "CLogFileSender" )
{
}

CLogFileSender::~CLogFileSender()
{
}

bool CLogFileSender::DoWork()
{
	std::vector< std::string > vecSearchingDirList = CDeamonConfig::GetInstance()->GetSearchingDirList();
	std::vector< std::string >::iterator it;

	for (it=vecSearchingDirList.begin(); it<vecSearchingDirList.end(); it++)
	{
		std::string szSearchingDir = *it;
		// 하위 폴더를 재귀 탐색 하면서 파일을 전송한다.
		SendFiles( szSearchingDir);
	}

	return true;
}

bool CLogFileSender::SendFiles( std::string path)
{
	/// 디렉토리 open.
	DIR* pDir = opendir( path.c_str() ); 

	if( pDir == NULL )
	{
		//_LOG( LINF, "can't send files because opendir(%s) is failed.", path.c_str() );
		_LOG( LINF, "[SEND] directory [%s] not exist.", path.c_str() );
		
		return false;
	}

	string fullname;
	struct dirent* pDirEnt;

	/// 디렉토리 내의 파일들을 검색.
	while( ( pDirEnt = readdir( pDir ) ) != NULL )
	{
		if( pDirEnt->d_name[0] == '.' )
		{
			continue;
		}

		/// 파일의 전체 경로 만들기.
		fullname = path + "/" + string( pDirEnt->d_name );

		if( IsDirectory( fullname ) == true )
		{
			/// 디렉토리이면 재귀 호출.
			SendFiles( fullname );
		}
		else
		{
			/// 로그 파일이 아니면 skip.
			// 아파치 로그파일 여부까지 확인한다.
			if( IsLogFile( fullname ) == false )
			{
				LOG( LDEV, "%s is not log file.", fullname.c_str() );
				continue;
			}


			// 파일이면 CInode 정보를 가지고 온다.
			CInode inode;
			if( inode.LoadInfo( fullname ) == false )
			{
				/// 파일 정보를 가지고 오지 못 하면 skip.
				LOG( LWAR, "inode info loading is failed. file=%s", fullname.c_str() );
				continue;
			}

			// 파일 접근 시간이 24시간이내면 당일 파일이라고 판단
			//if( (current - inode.GetATime() ) < SECONDS_CORRESPOND_TO_ONE_DAY )
			
			if( IsTodayFile( fullname ) == true )
			{
				_LOG( LDBG, "[%s] is skip.. because today's file.", fullname.c_str() );
				continue;
			}

			string szAppName;
			GetApplicationName(fullname, szAppName);


			// 파일을 전송한다.
			// 에러에 대한 체크는 체크는 하지 않는다.
			// 전송 실패 한 경우에 대해 logging 처리만하고.. skip한다.
			// 다음날 재전송 시도를 하기때문에...
			FileSending(szAppName, fullname);
		}
	}

	/// 디렉토리 close.
	closedir( pDir );

	return true;
}

bool CLogFileSender::IsTodayFile(string szFullName)
{
	time_t timestamp;
	struct tm timeNow;

	/// 현재 시각 확인
	timestamp = time( NULL );
	localtime_r( &timestamp, &timeNow );


	// 2017-03-23 CHG huibong
	// - #30594 의 내역 대로 Apache 로그 파일 형식은 access_log_20170322 와 같이 .log 확장자를 사용하지 않음.
	// - 따라서 이를 반영하기 위해 확장자 없이 파일명에 날짜 정보만 동일한지 체크하도록 수정 처리한다.
	//char timeStr[256];
	//snprintf( timeStr, (size_t)256, "%04d%02d%02d.log", timeNow.tm_year+1900, timeNow.tm_mon+1, timeNow.tm_mday);
	
	char timeStr[32];
	snprintf( timeStr, (size_t)32, "%04d%02d%02d", timeNow.tm_year+1900, timeNow.tm_mon+1, timeNow.tm_mday);


	string szDate = timeStr;
	std::string::size_type pos = szFullName.rfind( szDate );

	if( pos == std::string::npos )
	{
		return false;
	}
	else
	{
		// 파일명에 오늘 날짜 정보가 들어 있는 경우.
		return true;
	}
}

// 파일 전송처리하며... 실패하면 logging 처리만한다.
void CLogFileSender::FileSending(string szAppName,string szFullName)
{
	std::string szErrorMessage;
	unsigned long long uExistFileSize = 0;

	string szServer = CDeamonConfig::GetInstance()->GetLogServer();
	int nServerPort = CDeamonConfig::GetInstance()->GetLogServerPort();

	// 파일 Full 경로에서 파일명 추출 처리.
	std::string::size_type pos = szFullName.rfind( '/' );
	if( pos == std::string::npos )
	{
		// 찾지 못한 경우...
		LOG( LERR, "File path is FULL PATH use. [%s]\n", szFullName.c_str() );
		return ;
	}
	
	// 전송할 파일 정보
	std::string strTargetDir       = szFullName.substr( 0, pos );
	std::string strTragetFileName  = szFullName.substr( pos+1 );

	// 1. 전송 할 파일오픈
	// fd open
	int fd = open(szFullName.c_str(), O_RDONLY );
	if( fd == -1 )
	{
		int nErrNum = errno;
		LOG( LERR, "file[%s] open fail.[%d][%s]\n", szFullName.c_str(), nErrNum, strerror( errno ));

		return;
	}

	// 2. 전송 소켓 생성
	// socket 생성 및 연결
	CCcCollectdClientSocket serverSocket;
	if( serverSocket.ConnectTarget( szServer, nServerPort ) == false )
	{
		// 접속 실패시
		close( fd );

		LOG( LERR, "cc_collectd connect fail. [%s][%d]", szServer.c_str(), nServerPort );
		return;
	}

	// local file size 추출
	// 파일이면 CInode 정보를 가지고 온다.
	CInode inode;
	if( inode.LoadInfo( szFullName) == false )
	{
		LOG( LWAR, "inode info loading is failed. file=%s", szFullName.c_str() );
		return;
	}

    unsigned long long uFileSize = inode.GetSize();

	// 3. Log Backup 서버에 파일 존재여부 확인
	if( serverSocket.CheckFile( szAppName, strTragetFileName, uExistFileSize, szErrorMessage ) == false )
	{
		// 3-1. 파일 존재 여부확인이 에러가 나면 logging하고 skip 한다.
		close( fd );

		LOG( LERR, "cc_collectd exist check fail.[%s][%llu] -> [%s]", strTragetFileName.c_str(), uFileSize, szErrorMessage.c_str() );
		return;
	}
	else
	{
		LOG( LDBG, "cc_collectd exist check OK.local[%s][%llu] remote[%llu]", strTragetFileName.c_str(), uFileSize, uExistFileSize );

		// 3-2. 존재 하더라도 파일크기가 다른경우는 전송 처리한다.
		if( uFileSize == uExistFileSize )
		{
			LOG( LDBG, "cc_collectd same file exist. not send. local[%s][%llu] remote[%llu]", strTragetFileName.c_str(), uFileSize, uExistFileSize );
		}
		else
		{
			
			_LOG( LINF, "[%s] log File sending... filename is [%s]  ", szAppName.c_str(), szFullName.c_str() );
			// 파일 크기가 다른 경우에만 전송 처리.
			// Log 파일 전송 처리
			// CheckFile() 함수에서 파일이 존재하지 않을 경우 filesize를 0으로 리턴하므로 local의 log filesize가 '0'이 아니면 전송처리 됨.
			if( serverSocket.SendFile( szAppName, strTragetFileName, fd, uFileSize, szErrorMessage ) == false )
			{
				close( fd );

				LOG( LERR, "cc_collectd send fail.[%s][%llu] -> [%s]", strTragetFileName.c_str(), uFileSize, szErrorMessage.c_str() );
				return;
			}
			else
			{
				_LOG( LINF, "cc_collectd send OK.[%s][%llu]", strTragetFileName.c_str(), uFileSize );
			}
		}
	}

	// 4. 전송 소켓 close()는 CCcCollectdClientSocket 소멸자에 존재함
	// 5. fd close()
	close(fd);
}

bool CLogFileSender::GetApplicationName(string szFullName, string& szAppName)
{
	/// '/'을 구분자로한 토큰을 벡터에 넣는다.
	vector<string> tokens = CString::Tokenize( szFullName, "/" );

	/// 벡터의 크기가 1이면 '.'이 없다는 말이다.  즉, 로그 파일이 아니다.
	if( tokens.size() < 2 )
	{
		return false;
	}
	
	szAppName = tokens[ tokens.size() - 2 ];

	return true;
}
/// 파일의 확장자가 log이면 true 반환, 그렇지 않으면 false 반환.
bool CLogFileSender::IsLogFile( std::string filename )
{
	//!! 아파치 로그인지를 확인한다.
	int n1 = filename.find(ACCESS_LOG_PRE_NAME);
	int n2 = filename.find(ERROR_LOG_PRE_NAME);
	LOG( LDBG, "%s, %d, %d ", filename.c_str(), n1, n2 );

	//if( filename.find(ACCESS_LOG_PRE_NAME) >= 0 || filename.find(ERROR_LOG_PRE_NAME) >=0 )
	if( n1 >= 0 || n2 >=0 )
	{
		LOG( LDBG, "%s is apache log file.", filename.c_str() );
		return true;
	}

	/// '.'을 구분자로한 토큰을 벡터에 넣는다.
	vector<string> tokens = CString::Tokenize( filename, "." );

	/// 벡터의 크기가 1이면 '.'이 없다는 말이다.  즉, 로그 파일이 아니다.
	if( tokens.size() == 1 )
	{
		return false;
	}

	/// 파일 이름에서 확장자를 가지고 온다.
	string extension = tokens[ tokens.size() - 1 ];

	/// 확장자 비교.
	if( extension.compare( "log" ) != 0 )
	{
		/// 확장자가 'log'가 아님.
		return false;
	}

	return true;
}
