#include "stdafx.h"
#include "resource.h"
#include "FWatchApp.hpp"

#include "ProcessActionInvoker.hpp"

#include <shellapi.h>

#include <vector>
#include <algorithm>
#include <assert.h>

#include "Utility.hpp"
#include "StringExpander.hpp"


#include <strsafe.h>
#pragma comment(lib, "strsafe.lib")


//////////


// AbstractProcessExecActionInvokerの識別子
// {68759081-5EA0-4b9c-B9C9-3E834C0B911E}
static const GUID SHELLEXECUTE_ACTIONINVOKE_SIG
	= { 0x68759081, 0x5ea0, 0x4b9c, { 0xb9, 0xc9, 0x3e, 0x83, 0x4c, 0xb, 0x91, 0x1e } };


AbstractProcessExecActionInvoker::AbstractProcessExecActionInvoker(const CSettingInfo& v_settingInfo)
	: ActionInvoker(v_settingInfo)
	, runCount_(0)
{
}

AbstractProcessExecActionInvoker::~AbstractProcessExecActionInvoker() throw()
{
	// 保持している全てのプロセスハンドルを放棄する
	for( ProcessHandleMap::iterator ite = processHandleMap_.begin();
		ite != processHandleMap_.end();
		++ite ) {
		HANDLE hProcess = ite->first;
		::CloseHandle(hProcess);
	}
	processHandleMap_.clear();
}

void AbstractProcessExecActionInvoker::Reset()
{
	runCount_ = 0;
}

void AbstractProcessExecActionInvoker::Load(CObjectReader &reader)
{
	reader.VerifySig(SHELLEXECUTE_ACTIONINVOKE_SIG);
	runCount_ = reader.ReadDWORD();
}

void AbstractProcessExecActionInvoker::Save(CObjectWriter &writer)
{
	writer.WriteSig(SHELLEXECUTE_ACTIONINVOKE_SIG);
	writer.WriteDWORD(runCount_);
}

void AbstractProcessExecActionInvoker::addTraceProcessHandle(HANDLE v_hProcess, const tstring& v_absolutePath) throw()
{
	if (v_hProcess == NULL) {
		// ShellExecuteExでDDE通信で既存のプロセスを使用してドキュメントが開かれた場合はプロセスは新規に作られないのでNULLとなる。
		if (pLogger_ && pLogger_->GetLogThreshold() >= 10) {
			pLogger_->LogMessage(10, NULL, 0, _T("no process launched."));
		}
		return;
	}
	if ( !settingInfo_.isIgnoreDuplicate() && settingInfo_.getMaxProcess() <= 0) {
		// 無制限且つ重複起動を許可しているので追跡の必要ない。
		// 即座にハンドルを放棄する
		::CloseHandle(v_hProcess);

		if (pLogger_ && pLogger_->GetLogThreshold() >= 10) {
			pLogger_->LogMessage(10, NULL, 0, _T("close process handle"));
		}
	}
	else {
		processHandleMap_.insert(ProcessHandleMap::value_type(v_hProcess, v_absolutePath));

		// ログ
		if (pLogger_) {
			pLogger_->LogMessage(
				2,
				NULL,
				0,
				IDS_LOGMES_WATCHPROC,
				processHandleMap_.size(),
				settingInfo_.getMaxProcess(),
				v_absolutePath.c_str()
				);
		}
	}
}

bool AbstractProcessExecActionInvoker::isWatchedProcess(const tstring& v_absolutePath) const
{
	for( ProcessHandleMap::const_iterator ite = processHandleMap_.begin();
		ite != processHandleMap_.end();
		++ite) {
		const tstring& absolutePath = ite->second;
		if (v_absolutePath == absolutePath) {
			return true;
		}
	}
	return false;
}

bool AbstractProcessExecActionInvoker::canDoAction() throw()
{
	unsigned int maxProcess = settingInfo_.getMaxProcess();
	return maxProcess == 0 || sweepGarbage() < maxProcess;
}

unsigned int AbstractProcessExecActionInvoker::sweepGarbage() throw()
{
	unsigned int count = 0;
	for( ProcessHandleMap::iterator ite = processHandleMap_.begin();
		ite != processHandleMap_.end();) {
		HANDLE hProcess = ite->first;
		const DWORD ret = ::WaitForSingleObject(hProcess, 0);
		if (ret != WAIT_TIMEOUT) {
			// プロセスが終了していないことが確実でなければ終了しているとみなす。

			// プロセスハンドルを閉じる
			DWORD err = 0;
			if ( !::CloseHandle(hProcess)) {
				err = GetLastError();
			}

			// 監視リストから外す
			tstring absolutePath = ite->second;
			ite = processHandleMap_.erase(ite);

			// ログ
			if (pLogger_) {
				pLogger_->LogMessage(
					2,
					NULL,
					err,
					IDS_LOGMES_WATCHPROC_END,
					processHandleMap_.size(),
					settingInfo_.getMaxProcess(),
					absolutePath.c_str()
					);
			}
		}
		else {
			count++;
			++ite;
		}
	}
	return count;
}

DWORD AbstractProcessExecActionInvoker::internalGetRunCount()
{
	return runCount_;
}

/*!
 * アクションを実行します。
 * アクションの最大数を超えているかは判定されないため、
 * 呼び出しもとで適宜にcanDoActionでチェックする必要があります。
 * \param v_absolutePath ターゲットファイルの絶対パス
 * \param fileInfo 監視ファイル情報
 */
bool AbstractProcessExecActionInvoker::doAction(const tstring& v_absolutePath, const CFileInfo& fileInfo) throw()
{
	//required:
	assert( !v_absolutePath.empty() && "v_absolutePathに空は指定できません。");
	
	//do:

	// 同一ファイルの重複実行を許可していなければ、この通知は捨てる。
	if (settingInfo_.isIgnoreDuplicate() && isWatchedProcess(v_absolutePath)) {
		pLogger_->LogMessage(
			2,
			NULL,
			0,
			IDS_LOGMES_APPLIC_DUPLICATE,
			v_absolutePath.c_str()
			);
		return true;
	}

	// 派生もとを実行
	return ActionInvoker::doAction(v_absolutePath, fileInfo);
}


//////////


ShellExecActionInvoker::ShellExecActionInvoker(const CSettingInfo& v_settingInfo)
	: AbstractProcessExecActionInvoker(v_settingInfo)
{
}

ShellExecActionInvoker::~ShellExecActionInvoker() throw()
{
}

/*!
 * プロセスの実行
 * \param v_absoluePath ターゲットの絶対パス
 * \param v_fileInfo ターゲットのファイル情報
 * \param verb アクションタイプ
 * \param appName アプリケーション名(展開済み)
 * \param param パラメータ(展開済み)
 * \param appCurrentDir カレントディレクトリ(展開済み)
 */
bool ShellExecActionInvoker::internalDoAction(
	const tstring v_absolutePath,
	const CFileInfo&,
	const tstring& verb,
	const tstring& appName, 
	const tstring& param,
	const tstring& appCurrentDir) throw()
{
	//required:
	assert( !v_absolutePath.empty() && "v_absolutePathに空は指定できません。");
	
	//do:

	// 総実行回数をカウントする
	runCount_++;

	// 起動アプリケーションが指定されていなければ何もしない.
	if (settingInfo_.getAppName().empty()) {
		return true;
	}

	// 起動引数

	SHELLEXECUTEINFO execinfo = {0};
	execinfo.cbSize = sizeof(SHELLEXECUTEINFO);
	execinfo.fMask  = SEE_MASK_CONNECTNETDRV | SEE_MASK_DOENVSUBST | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
	execinfo.hwnd   = NULL;
	execinfo.lpDirectory  = NULL; // 既定: つまりFWatch.exe起動時のカレント
	execinfo.nShow  = settingInfo_.getShowWindow().getShowCommand();
	execinfo.lpVerb = verb.c_str();
	execinfo.lpFile = appName.c_str();
	execinfo.lpParameters = param.c_str();

	if ( !appCurrentDir.empty()) {
		// カレントディレクトリの指定がある場合は、それを設定する。
		execinfo.lpDirectory = appCurrentDir.c_str();

	}
	else {
		if (app.IsCurrentDirSetToWatchDir()) {
			// アプリケーション起動時のカレントディレクトリを監視対象フォルダにする
			execinfo.lpDirectory = settingInfo_.getWatchDir().c_str();
		}
	}

	// ログ
	if (pLogger_) {
		pLogger_->LogMessage(
			2,
			NULL,
			0,
			IDS_LOGMES_APPLIC,
			execinfo.lpVerb,
			execinfo.lpFile,
			execinfo.lpParameters,
			(execinfo.lpDirectory == NULL) ? _T("") : execinfo.lpDirectory
			);
	}

	// 実行
	DWORD err = ERROR_SUCCESS;
	if (ShellExecuteEx(&execinfo)) {
		addTraceProcessHandle(execinfo.hProcess, v_absolutePath);
	}
	else {
		err = GetLastError();
		if (pLogger_) {
			pLogger_->LogMessage(
				0,
				NULL,
				err,
				IDS_LOGMES_APPLICFAIL,
				appName.c_str()
				);
		}
	}

	return err == ERROR_SUCCESS;
}



//////////


CreateProcessAsLogonActionInvoker::CreateProcessAsLogonActionInvoker(const CSettingInfo& v_settingInfo)
	: AbstractProcessExecActionInvoker(v_settingInfo)
	, verbType_(CreateProcessAsLogonActionInvoker::VERB_PROCESS)
{
	const tstring& verb = v_settingInfo.getAction();

	const tstring runas(_T("*RUNAS"));
	if (verb.compare(0, runas.length(), runas) == 0) {
		// *RUNASアクションの場合

		if (verb.find(_T(";load_profile=true")) != verb.npos) {
			// プロファイルのロードオプションが指定されている場合
			verbType_ = CreateProcessAsLogonActionInvoker::VERB_RUN_AS_USER_WITH_PROFILE;
		}
		else {
			// プロファイルのロードオプションが指定されていない場合
			verbType_ = CreateProcessAsLogonActionInvoker::VERB_RUN_AS_USER;
		}
	}
}

CreateProcessAsLogonActionInvoker::~CreateProcessAsLogonActionInvoker() throw()
{
}

/*!
 * プロセスの実行
 * \param v_absoluePath ターゲットの絶対パス
 * \param v_fileInfo ターゲットのファイル情報
 * \param verb アクションタイプ
 * \param appName アプリケーション名(展開済み)
 * \param param パラメータ(展開済み)
 * \param appCurrentDir カレントディレクトリ(展開済み)
 */
bool CreateProcessAsLogonActionInvoker::internalDoAction(
	const tstring v_absolutePath,
	const CFileInfo&,
	const tstring& verb,
	const tstring& appName, 
	const tstring& param,
	const tstring& appCurrentDir) throw()
{
	//required:
	assert( !v_absolutePath.empty() && "v_absolutePathに空は指定できません。");
	
	//do:

	// 総実行回数をカウントする
	runCount_++;

	// 起動アプリケーションとパラメータの双方が指定されていなければ何もしない.
	// (アプリケーションを省略してパラメータのみで起動する場合がありえる。)
	if (settingInfo_.getAppName().empty() && settingInfo_.getParam().empty()) {
		return true;
	}

	// カレントディレクトリの指定がない場合
	tstring appCurrentDirSw(appCurrentDir);
	if (appCurrentDir.empty()) {
		if (app.IsCurrentDirSetToWatchDir()) {
			// 設定ファイルが監視ディレクトリをカレントにするように指定されていれば、
			// アプリケーション起動時のカレントディレクトリを監視対象フォルダにする。
			appCurrentDirSw = settingInfo_.getWatchDir();
		}
	}

	STARTUPINFO startupInfo = {0};
	startupInfo.cb = sizeof(STARTUPINFO);
	startupInfo.wShowWindow = (WORD) settingInfo_.getShowWindow().getShowCommand();

	PROCESS_INFORMATION processInformation = {0};

	std::vector<TCHAR> paramBuf;
	std::copy(param.begin(), param.end(), std::back_inserter(paramBuf));
	paramBuf.push_back(0);


	DWORD err = ERROR_SUCCESS;
	BOOL ret = FALSE;

#ifdef _UNICODE
	if (verbType_ != VERB_PROCESS && !settingInfo_.getImpersonateUser().empty()) {
		// VERB_PROCESSでなく、且つ、ユーザが指定されている場合
		// (およびUNICODEビルド時のみ)
		DWORD dwLogonFlags = (verbType_ == VERB_RUN_AS_USER_WITH_PROFILE ? LOGON_WITH_PROFILE : 0);
		// ログ
		if (pLogger_) {
			pLogger_->LogMessage(
				2,
				NULL,
				0,
				IDS_LOGMES_APPLIC_RUNAS,
				settingInfo_.getImpersonateUser().c_str(),
				settingInfo_.getImpersonateDomain().c_str(),
				(verbType_ == VERB_RUN_AS_USER_WITH_PROFILE ? _T("true") : _T("false")),
				verb.c_str(),
				appName.c_str(),
				param.empty() ? _T("") : &paramBuf[0],
				appCurrentDir.c_str()
				);
		}
		ret = CreateProcessWithLogonW(
			settingInfo_.getImpersonateUser().c_str(),
			settingInfo_.getImpersonateDomain().empty() ? NULL : settingInfo_.getImpersonateDomain().c_str(),
			settingInfo_.getImpersonatePassword().c_str(),
			dwLogonFlags, // dwLogonFlags
			appName.empty() ? NULL : appName.c_str(),
			param.empty() ? NULL : &paramBuf[0],
			0, // dwCreationFlags
			NULL, // lpEnviroment
			appCurrentDirSw.empty() ? NULL : appCurrentDirSw.c_str(),
			&startupInfo,
			&processInformation);
	}
	else
#endif
	{
		// ログ
		if (pLogger_) {
			pLogger_->LogMessage(
				2,
				NULL,
				0,
				IDS_LOGMES_APPLIC_PROCESS,
				verb.c_str(),
				appName.c_str(),
				param.empty() ? _T("") : &paramBuf[0],
				appCurrentDir.c_str()
				);
		}
		ret = CreateProcess(
			appName.empty() ? NULL : appName.c_str(),
			paramBuf.empty() ? NULL : &paramBuf[0],
			NULL, // processAttributes
			NULL, // threadAttributes
			FALSE, // inherit handles
			0, // CREATION FLAGS
			NULL, // environment
			appCurrentDirSw.empty() ? NULL : appCurrentDirSw.c_str(),
			&startupInfo,
			&processInformation);
	}

	if (ret) {
		// プロセス起動に成功した場合
		addTraceProcessHandle(processInformation.hProcess, v_absolutePath);
		CloseHandle(processInformation.hThread);
	}
	else {
		// プロセス起動に失敗した場合
		err = GetLastError();
		if (pLogger_) {
			pLogger_->LogMessage(
				0,
				NULL,
				err,
				IDS_LOGMES_APPLICFAIL,
				appName.c_str()
				);
		}
	}

	return err == ERROR_SUCCESS;
}
