#include "StdAfx.h"
#include "FileResponse.h"
#include "DateUtility.h"
#include "TextTools.h"
#include "Dir.h"
#include "MimeTypeManager.h"

//!	モジュール名
static TCHAR _MODULE_NAME[] = _T("FileResponse");

CFileResponse::CFileResponse(void)
{
}

CFileResponse::~CFileResponse(void)
{
}


//////////////////////////////////////////////////////////////////////////////////////////////
//	初期化
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	初期化
*/
void CFileResponse::Start(CIConnectionToolWrap responseContext)
{
	m_bodyIsSended = 0;
	m_file = NULL;
}


/*!
	停止(例外発生厳禁！)
*/
void CFileResponse::Stop(CIConnectionToolWrap responseContext)
{
	//	閉じておく
	SAFE_CLOSE_HANDLE(m_file);
}

/*!
	レスポンス名取得
*/
LPCTSTR CFileResponse::GetResponseName()
{
	return _MODULE_NAME;
};


/*!
	レスポンス生成

	サーバレスポンス発生は、データ受信後の方が都合が良い
*/
void CFileResponse::BuildResponse(CIConnectionToolWrap responseContext)
{
	//	リクエスト取得
	CContextRequestInfo requestInfo = responseContext.GetContxet().GetRequestInfo();

	//	ターゲット取得
	CContextTargetInfo targetInfo = responseContext.GetContxet().GetTargetInfo();

	//	GET? or HEAD?
	if(requestInfo.GetMethod() == "GET")
		m_bodyIsSended = FALSE;
	else if(requestInfo.GetMethod() == "HEAD")
		m_bodyIsSended = TRUE;
	else
		throw CServerResponseException(405, _T("非対応のメソッドです"));

	//	無い？
	if(!targetInfo.GetTargetIsExist())
		throw CServerResponseException(404, _T("指定されたファイルは存在しません"));

	//	ファイル？
	if(targetInfo.GetTargetIsDirectory() || targetInfo.GetTarget().Right(1) == "\\")
		throw CConnectionErrorException(_T("ディレクトリをファイルとして表示しようとしました"));

	//	開く
	OpenFile(targetInfo.GetTarget());

	//	ETag・日付チェック
	if(CheckUpdate(responseContext.GetContxet(), targetInfo.GetTarget()))
		throw CServerResponseException(304, _T("ターゲットは更新されていません"));

	//	レジューム
	CheckRange(responseContext.GetContxet());
}


/*!
	レスポンスヘッダ取得
*/
void CFileResponse::GetResponse(CIConnectionToolWrap responseContext)
{
	CContextResponseInfo responseInfo = responseContext.GetContxet().GetResponseInfo();

	//	レスポンスコード設定
	if(m_startAt == 0)
		responseInfo.SetResponseCode(200);
	else
		responseInfo.SetResponseCode(206);

	//	レスポンスサイズ
	if(m_bodyIsSended)
	{
		//	レスポンスサイズクリア
		responseInfo.GetResponseOption().SetContentLength(0);
	}
	else
	{
		//	レスポンスサイズ設定
		responseInfo.GetResponseOption().SetContentLength(m_fileSize - m_startAt);
	}

	//	MimeType
	responseInfo.GetResponseOption().SetContentType(responseContext.GetIServerTool().GetIMimeTypeManager().
				GetMimeTypeByName(CPathTools::PathToName(responseContext.GetContxet().GetTargetInfo().GetTarget())));
}

/*!
	レスポンスボディ取得
*/
int CFileResponse::GetResponseBody(CIConnectionToolWrap responseContext, CBinaryData &body, int blockSize)
{
	//	Head?
	if(m_bodyIsSended)
		return 0;

	//	バッファサイズ修正
	body.ReSize(blockSize);

	//	読んでみる
	DWORD	readed = 0;
	if(!::ReadFile(m_file, body.GetPtr(), body.GetSize(), &readed, NULL))
	{
		m_file = NULL;
		throw CConnectionErrorException(_T("ファイルの操作に失敗しました"));
	}
	body.ReSize(readed);

	//	終了
	if(readed != blockSize)
	{
		SAFE_CLOSE_HANDLE(m_file);
		m_bodyIsSended = TRUE;
	}

	//	サイズを返す
	return(readed);
}

//////////////////////////////////////////////////////////////////////////////////////////////
//	ツール関数
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	ファイルを開く
*/
void CFileResponse::OpenFile(CString target)
{
	//	ファイルを開く
	m_file = ::CreateFile(target, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if(m_file == INVALID_HANDLE_VALUE)
		throw CConnectionErrorException(_T("ファイルの操作に失敗しました"));

	//	ファイル情報取得
	BY_HANDLE_FILE_INFORMATION	fInfo;
	if(!::GetFileInformationByHandle(m_file, &fInfo))
		throw CConnectionErrorException(_T("ファイルの操作に失敗しました"));

	//	情報設定
	m_fileSize = ((__int64)fInfo.nFileSizeHigh << 32) + (__int64)fInfo.nFileSizeLow;
	m_lastUpdate = fInfo.ftLastWriteTime;
	m_startAt = 0;
}


/*!
	レジューム
*/
void CFileResponse::CheckRange(CContext context)
{
	//	リクエスト取得
	CContextRequestOption requestOption = context.GetRequestInfo().GetRequestOption();

	//	レスポンス取得
	CContextResponseOption responseOption = context.GetResponseInfo().GetResponseOption();

	//	Rangeヘッダは？
	CString range = requestOption.GetRange();
	if(range != "")
	{
		//	サポートしてるのか？(bytesか？)
		//	Range: bytes=xxxx-
		if(range.Left(6).CompareNoCase("bytes=")==0)
		{
			//	開始位置を切り出す
			range = range.Mid(6);
			int index = range.Find("-");
			if(index != -1)
				range = range.Left(index);

			//	開始位置チェック
			__int64	start = _atoi64(range);
			if(start < 0 || start >= m_fileSize)
				throw CServerResponseException(416, _T("無効な開始位置指定です"));

			//	開始位置変更
			m_startAt = start;
			LARGE_INTEGER	liStart;
			liStart.QuadPart = start;
			if(!SetFilePointerEx(m_file, liStart, NULL, FILE_BEGIN))
				throw CConnectionErrorException(_T("ファイルの操作に失敗しました"));

			//	ヘッダ修正
			responseOption.SetAcceptRanges("bytes");
			responseOption.SetContentRange(CTextTools::GetFormat("bytes %I64d-%I64d/%I64d", m_startAt, m_fileSize, m_fileSize));
		}
	}
}


/*!
	更新確認
*/
int CFileResponse::CheckUpdate(CContext context, CString target)
{
	//	リクエスト取得
	CContextRequestOption requestOption = context.GetRequestInfo().GetRequestOption();

	//	レスポンス取得
	CContextResponseOption responseOption = context.GetResponseInfo().GetResponseOption();


	//	Last-Modified設定
	responseOption.SetLastModified(CDateUtility::GetGMTString(m_lastUpdate));

	//	ETag生成
	CString	etag = GetETag(target);

	//	ETag設定
	responseOption.SetETag(etag);

	//	時刻比較
	CTime ifModif = CDateUtility::StringToCTime(requestOption.GetIfModifiedSince());
	if(ifModif != 0 && m_lastUpdate <= ifModif)
		return(TRUE);

	//	ETag比較
	if(requestOption.GetIfNoneMatch() == etag)
		return(TRUE);

	return(FALSE);
}

/*!
	ETag取得
*/
CString	CFileResponse::GetETag(CString target)
{
	//	ターゲットパス
	target = target.MakeUpper();

	//	ファイル名
	int	calc=0;
	for(int i=0;i<target.GetLength();i++)
		calc += (int)target[i];

	//	作成
	CString str;
	str.Format("\"%I64x-%I64x:%04d\"",m_fileSize, m_lastUpdate.GetTime(), calc);

	return(str);
}
