#include "stdafx.h"

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

#include "Utility.hpp"

#include "ImpersonateUser.hpp"
#include "WatchThread.hpp"

#include <process.h>
#include <assert.h>

#include <string>


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

namespace
{

	/*!
	 * 監視ハンドルのリソース管理クラス
	 */
	class WatchHandle
	{
	private:
		const CSettingInfo& settingInfo_;

		FILETIME creation_;

		FILETIME lastChallenged_;

		HANDLE findHandle_;

		Logger* pLogger_;

	public:
		WatchHandle(const CSettingInfo& settingInfo)
			: settingInfo_(settingInfo)
			, creation_(FILETIME())
			, lastChallenged_(FILETIME())
			, findHandle_(INVALID_HANDLE_VALUE)
		{
		}

		~WatchHandle()
		{
			Close();
		}

		void SetLogger(Logger* pLogger)
		{
			pLogger_ = pLogger;
		}

		Logger* GetLogger() const
		{
			return pLogger_;
		}

		void Close()
		{
			if (findHandle_ != INVALID_HANDLE_VALUE) {
				DWORD err = 0;
				if (!::FindCloseChangeNotification(findHandle_)) {
					err = GetLastError();
				}

				findHandle_ = INVALID_HANDLE_VALUE;

				if (pLogger_) {
					pLogger_->LogMessage(4, NULL, err, IDS_LOGMES_WATCH_CLOSE);
				}
			}
		}

		bool checkAndRenew()
		{
			if ( !settingInfo_.isUsingDirNotificationAPI()) {
				// ファイル監視ハンドルを使用しない場合は何もしない
				return false;
			}

			FILETIME ftNow;
			::GetSystemTimeAsFileTime(&ftNow);

			DWORD retryInterval = settingInfo_.getDirNotificationAPIRetryInterval();
			DWORD expireSpan = settingInfo_.getDirNotificationAPIExpirySpan();

			// ハンドル作成後、有効期間を超えていればハンドルをクローズする。
			bool renew = false;
			if (findHandle_ != INVALID_HANDLE_VALUE) {
				if (expireSpan > 0 && IsExpired(ftNow, expireSpan * 1000L, creation_)) {
					Close();
					renew = true;
				}
			}

			if (findHandle_ != INVALID_HANDLE_VALUE) {
				// ハンドルが有効であれば何もせず戻る。
				return true;
			}

			if (!renew && retryInterval > 0 && !IsExpired(ftNow, retryInterval * 1000L, lastChallenged_)) {
				// 最後にリトライしてから指定秒数経過していない場合はハンドルを作成せずに戻る
				return false;
			}

			// 作成試行日時を現在時刻にする
			lastChallenged_ = ftNow;

			// 監視ハンドルの作成
			PropertyStringExpander expander;
			app.setAppConfVariablesTo(expander);
			tstring watchDir = expander.expandString(settingInfo_.getWatchDir());

			findHandle_ = ::FindFirstChangeNotification(
				watchDir.c_str(),
				settingInfo_.getMaxDepth() > 0 ? TRUE : FALSE,
				FILE_NOTIFY_CHANGE_FILE_NAME
					| (settingInfo_.getMaxDepth() > 0 ? FILE_NOTIFY_CHANGE_DIR_NAME : 0)
					| (settingInfo_.isArchiveOnly() ? FILE_NOTIFY_CHANGE_ATTRIBUTES : 0)
					| FILE_NOTIFY_CHANGE_LAST_WRITE
					| FILE_NOTIFY_CHANGE_SIZE
				);
			if (findHandle_ != INVALID_HANDLE_VALUE) {
				// ハンドルが作成された場合、作成日時をセット
				creation_ = ftNow;

				if (pLogger_) {
					pLogger_->LogMessage(
						4,
						NULL,
						0,
						renew ? IDS_LOGMES_WATCH_RENEW : IDS_LOGMES_WATCH_CREATED
						);
				}

			}
			else {
				// ハンドルが作成できなかった場合
				if (pLogger_) {
					DWORD errcode = GetLastError();
					pLogger_->LogMessage(0, NULL, errcode, IDS_LOGMES_WATCHERROR);
				}
			}

			return findHandle_ != INVALID_HANDLE_VALUE;
		}

		operator HANDLE() const
		{
			return findHandle_;
		}

	};

}

/*!
 * 停止イベントが発生していれば停止例外をスローして大域ジャンプさせるヘルパ。
 */
void CWatchThreadInterruptedException::checkAndThrow(HANDLE hStopEvent)
{
	if (!hStopEvent) {
		throw CWatchThreadInterruptedException();
	}
	DWORD waitResult = WaitForSingleObject(hStopEvent, 0);
	if (waitResult != WAIT_TIMEOUT) {
		// 停止イベントによる緊急離脱
		throw CWatchThreadInterruptedException();
	}
}


////

CWatchThread::CWatchThread(const CSettingInfo& v_settingInfo)
	: settingInfo_(v_settingInfo)
	, fileTimeComparator_(v_settingInfo.getTolerance())
	, nThreadID_(0)
	, hThread_(NULL)
	, nameMatcher_(v_settingInfo.getWatchFile())
{
	hStopEvent_ = ::CreateEvent( NULL, TRUE, FALSE, NULL );
	assert(hStopEvent_ != NULL && "停止イベントハンドルを取得できません。");

	// 基本設定の取得
	baseCycle_ = app.GetLong(_T("CONFIG"), _T("BASE_CYCLE"), 10);
	spanSweepDeletedEntries_ = app.GetLong(_T("CONFIG"), _T("SPAN_SWEEP_DELETED_ENTRIES"), 100);
	spanHeapCompaction_ = app.GetLong(_T("CONFIG"), _T("SPAN_HEAP_COMPACTION"), 600 * 1000);
	spanSavePersist_ = app.GetLong(_T("CONFIG"), _T("SPAN_SAVE_PERSIST"), 300 * 1000);
	nResetRunCount_ = app.GetLong(_T("CONFIG"), _T("RESET_RUN_COUNT"), 0);
	bPersistAtForceInterval_ = app.GetLong(_T("CONFIG"), _T("PERSIST_AT_FORCEINTERVAL"), 1) != 0;
	bContinueWhenFailedImpersonate_ = app.GetLong(_T("SECURITY"), _T("CONTINUE_WHEN_FAILED_IMPERSONATING"), 0) != 0;
	bRevertToSelfOnAction_ = app.GetLong(_T("SECURITY"), _T("REVERT_TO_SELF_ON_ACTION"), 0) != 0;

	// ファイルロガーの作成
	FileLogger* pFileLogger = new FileLogger();
	pFileLogger->SetLogPath(settingInfo_.getLog().c_str());
	pFileLogger->SetEnableLogging(settingInfo_.isEnableLogging());
	pFileLogger->SetLogThreshold(settingInfo_.getLogLevel());
	pLogger_.reset(pFileLogger);

	// アクション実行オブジェクトの作成
	ActionInvokerFactory actionInvokerFactory;
	pActionInvoker_ = actionInvokerFactory.create(settingInfo_);
	pActionInvoker_->setLogger(pFileLogger);
}


CWatchThread::~CWatchThread()
{
	Stop();
	::CloseHandle(hStopEvent_);
}


const CSettingInfo& CWatchThread::getSettingInfo() const
{
	return settingInfo_;
}


bool CWatchThread::isRunning() const
{
	if (hThread_ == NULL) {
		return false;
	}
	return (::WaitForSingleObject(hThread_, 0) == WAIT_TIMEOUT);
}


void CWatchThread::StopNoWait()
{
	if (hThread_ == NULL) {
		return;
	}

	::SetEvent(hStopEvent_);
}


void CWatchThread::Stop()
{
	if (hThread_ == NULL) {
		return;
	}

	// 停止要求
	StopNoWait();

	// スレッドが停止するのを待機する。
	::WaitForSingleObject(hThread_, INFINITE);

	// スレッドが停止したのでハンドルを解放する。
	::CloseHandle(hThread_);
	hThread_ = NULL;
}


void CWatchThread::Start()
{
	if (hThread_ != NULL) {
		if (WaitForSingleObject(hThread_, 0) == WAIT_TIMEOUT) {
			// すでに開始されているため何もしない。
			return;
		}

		// 停止済みのスレッドなのでハンドルを解放する。
		::CloseHandle(hThread_);
		hThread_ = NULL;
	}

	// ステータスの設定とスレッドの開始
	::ResetEvent(hStopEvent_);
	hThread_ = (HANDLE) _beginthreadex(NULL, 0, ThreadProc, this, CREATE_SUSPENDED, (unsigned int*) &nThreadID_);
	if (hThread_ != NULL) {
		::ResumeThread(hThread_);
	}
}

/*!
 * ファイルの絶対パスを指定して、そのファイルのファイルサイズを取得する。
 * また、同時に、ファイルが読み込みモードでオープンできるかチェックし、できない場合はfalseを返します。
 * falseが返された場合は格納先は変更されません。
 * Win32エラーコードを取得する場合はGetLastError()を呼び出します。
 * \param v_path ファイルの絶対パス
 * \param pSize ファイルサイズの保存先、NULLの場合は格納しない
 * \param pLastModified 最終更新日の保存先、NULLの場合は格納しない
 * \param pAttrs 属性の保存先、NULLの場合は格納しない
 * \return ファイルサイズの取得に失敗した場合
 */
bool CWatchThread::getFileInfo(const tstring& v_path, unsigned __int64 *pSize, FILETIME *pLastModified, DWORD *pAttrs) throw()
{
	//required:
	assert(!v_path.empty() && "ファイル名に空文字は指定できません。");
	
	//do:
	bool result = false;
	DWORD errcode = NO_ERROR;
	HANDLE fileHandle = ::CreateFile(v_path.c_str(),
			GENERIC_READ, // 少なくとも読み込みオープンできること。
			FILE_SHARE_READ | FILE_SHARE_WRITE,
			NULL,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			NULL);
	if (fileHandle == INVALID_HANDLE_VALUE) {
		errcode = GetLastError();
	}
	else {
		BY_HANDLE_FILE_INFORMATION fileData = {0};

		if ( !::GetFileInformationByHandle(fileHandle, &fileData)) {
			errcode = GetLastError();
		}
		else {
			ULARGE_INTEGER size = {fileData.nFileSizeLow, fileData.nFileSizeHigh};
			if (pSize) {
				*pSize = size.QuadPart;
			}
			if (pLastModified) {
				*pLastModified = fileData.ftLastWriteTime;
			}
			if (pAttrs) {
				*pAttrs = fileData.dwFileAttributes;
			}
			result = true;
		}

		::CloseHandle(fileHandle);
	}

	::SetLastError(errcode);
	return result;
}


/*!
 * 削除予定のエントリのうち、猶予期間を過ぎたエントリを除去する。
 * 削除されたエントリの数を返します。
 * \param ftNow 現在時刻
 * \param bSkipDeletePending 削除予定ファイルが一つもない場合にtrueに設定される。
 * \return 削除されたエントリの数
 */
int CWatchThread::sweepDeletedEntries(const FILETIME &ftNow, bool &bSkipDeletePending)
{
	const DWORD pendingLimit = (DWORD) (settingInfo_.getDeletePending() * 100);

	int numOfModified = 0; // エントリの変更数
	int numOfDeletePending = 0; // 削除待機中のファイル数

	for (FileNameList::iterator ite_pendingList = pendingList_.begin();
		ite_pendingList != pendingList_.end();) {
		const tstring& absoluteName = *ite_pendingList;
		CFileInfoMap::iterator ite_fileInfo = fileInfoMap_.find(absoluteName);
		
		bool removed = false;
		if (ite_fileInfo == fileInfoMap_.end()) {
			// 削除待機リストに、ファイルエントリマップに存在しないファイルが指定されている場合、
			// すでに削除済みなので削除待機リストから削除する。
			// (削除予定はエントリの走査時に都度再作成され同期されているはずなので本来ありえない。)
			assert(false && "削除予定にエントリにない項目が設定されています。");
			removed = true;
		}
		else {
			CFileInfo& fileInfo = ite_fileInfo->second;
			if (fileInfo.isExist()) {
				// ファイルが実在するので削除待機リストから除去する
				// (削除予定はエントリの走査時に都度再作成され同期されているはずなので本来ありえない。)
				assert(false && "削除予定にエントリ上実在する項目が設定されています。");
				removed = true;
			}
			else if (fileInfo.isDeletePending()) {
				if (pendingLimit == 0 || IsExpired(ftNow, pendingLimit, fileInfo.getDeletePendingBegin())) {
					// 削除待機対象であり、即座削除か、指定時間を経過しているので
					// ファイルエントリマップと、削除待機リストの双方から削除する。
					fileInfoMap_.erase(ite_fileInfo);
					removed = true;
				}
				else {
					// 削除待ちのファイルの個数をカウントする。
					numOfDeletePending++;
				}
			}
		}

		if (removed) {
			// 詳細ログ
			if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 6) {
				pLogger_->LogMessage(6, NULL, 0, IDS_LOGMES_REMOVEENTRY, absoluteName.c_str());
			}

			// 削除
			numOfModified++;
			ite_pendingList = pendingList_.erase(ite_pendingList);
			continue;
		}
		++ite_pendingList;
	}

	// 削除待ちファイル数がゼロであるか判定する。
	assert(numOfDeletePending >= 0 && "削除待ちファイル数のカウントに誤りがあります。");
	if (numOfDeletePending == 0) {
		// 削除待ちファイルがゼロであれば、次にファイルエントリが変更されるまで
		// 削除判定処理は不要であることを示すフラグ
		bSkipDeletePending = true;
		if (pLogger_->GetLogThreshold() >= 10) {
			pLogger_->LogMessage(10, NULL, 0, _T("no undecided file to delete."));
		}
	}

	assert(numOfModified >= 0 && "削除ファイル数が不正です。");
	return numOfModified;
}

/*!
 * 監視ディレクトリを走査します。
 * v_enableEventがtrueであれば、追加または更新による確定待ちフラグをたてます。
 * falseの場合はエントリを追加・更新・削除するだけで確定待ちは発生しません。
 * 追加・変更・削除されたエントリの数を返します。
 * \param ftNow 現在日時
 * \param v_enableEvent 確定待ちフラグをたてる場合はtrue
 * \return 追加・変更・削除されたエントリの数
 */
int CWatchThread::EnumFile(const FILETIME &ftNow, const bool v_enableEvent)
{
	PropertyStringExpander expander;
	app.setAppConfVariablesTo(expander);
	tstring watchDir = expander.expandString(settingInfo_.getWatchDir());

	int numOfModified = 0;
	if (EnumFileRecursive(ftNow, watchDir, 0, v_enableEvent, numOfModified)) {
		// 走査では、開始ディレクトリのファイル列挙が成功した場合のみ削除待機リストをリセットしています。
		// したがって、開始ディレクトリが失敗した場合は削除処理は実行できません。
		// (列挙そのものが失敗した場合は一時的な障害が理由の可能性があり、ファイルが存在しないかどうかも不明な為。)

		// 一旦削除キューをクリアして再作成する。
		pendingList_.clear();

		for (CFileInfoMap::iterator ite_fileInfo = fileInfoMap_.begin();
			ite_fileInfo != fileInfoMap_.end();) {
			const tstring& absolutePath = ite_fileInfo->first;
			CFileInfo& fileInfo = ite_fileInfo->second;

			// 停止イベントが発生しているか確認する。
			CWatchThreadInterruptedException::checkAndThrow(hStopEvent_);

			if ( !fileInfo.isExist()) {
				// エントリに存在するが走査の結果、実在しないものの場合

				if (settingInfo_.getDeletePending() <= 0) {
					// 削除待機なしの場合は即座にエントリから消して良い
					numOfModified++;

					// 詳細ログ
					if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 6) {
						pLogger_->LogMessage(6, NULL, 0, IDS_LOGMES_REMOVEENTRY, absolutePath.c_str());
					}

					ite_fileInfo = fileInfoMap_.erase(ite_fileInfo);
					continue;
				}

				if ( !fileInfo.isDeletePending()) {
					// 削除予定でないものは新たに削除予定にする。
					// (すでに削除予定のものは、そのまま。)
					fileInfo.setDeletePending(true);
					fileInfo.setDeletePendingBegin(ftNow);
				}

				// 削除予定キューに入れる。
				// (以前に削除予定になっているものも含む。)
				pendingList_.push_back(absolutePath);

				// 詳細ログ
				if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 6) {
					pLogger_->LogMessage(6, NULL, 0, IDS_LOGMES_REMOVEPENDING, absolutePath.c_str(), fileInfo.str().c_str());
				}
			}
			++ite_fileInfo;
		}
	}
	// 追加・更新・削除されたエントリの数を返す
	assert(numOfModified >= 0 && "追加・更新・削除ファイル数が不正です。");
	return numOfModified;
}

/*!
 * 監視ディレクトリを再帰的に走査します。
 * \param ftNow 現在日時
 * \param v_baseDir 走査ディレクトリ
 * \param v_depth 探索深さの現在位置
 * \param v_enableEvent 確定待ちフラグをたてる場合はtrue
 * \param numOfModified 監視リストに変更があった個数を格納する参照
 * \return 初回(depth=0)ディレクトリ走査に成功すればtrue、そうでなければfalse
 */
bool CWatchThread::EnumFileRecursive(const FILETIME &ftNow,
									 const tstring& v_baseDir,
									 const int v_depth,
									 const bool v_enableEvent,
									 int &numOfModified)
{
	// 検査対象パスを生成する
	const tstring baseDir = EnsureLast(v_baseDir, _T("\\"));

	// 詳細ログ
	if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 8) {
		pLogger_->LogMessage(8, NULL, 0, IDS_LOGMES_SCAN, baseDir.c_str());
	}
	
	// 変更があったと思われるファイルを検索する
	WIN32_FIND_DATA finddata = {0};
	HANDLE findHandle = ::FindFirstFile((baseDir + _T("*.*")).c_str(), &finddata);
	if (findHandle == INVALID_HANDLE_VALUE) {
		// 検査対象にアクセス不可能ならば何もしないで戻る。
		DWORD err = GetLastError();
		if (v_depth == 0) {
			// 最初のディレクトリのアクセス不可の場合、エラーとする。
			pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_FINDERROR, v_baseDir.c_str());
		} else {
			// サブディレクトリのアクセス不可はログレベル3
			pLogger_->LogMessage(3, NULL, err, IDS_LOGMES_FINDERROR, v_baseDir.c_str());
		}
		return false;
	}

	// 最初のフォルダ(走査ルート)をこれから開始する場合、まず、
	// 存在しないファイルを削除するため、すべてのファイル存在フラグをリセットする。
	// (ネットワークの一時的な切断の可能性のため、検査対象にアクセスできる場合のみ実行する。)
	if (v_depth == 0) {
		for (CFileInfoMap::iterator ite = fileInfoMap_.begin();
			ite != fileInfoMap_.end();
			++ite)
		{
			CFileInfo& fileInfo = ite->second;
			fileInfo.setExist(false);
		}
	}

	for (;;) {
		// 停止イベントが発生しているか確認する。
		CWatchThreadInterruptedException::checkAndThrow(hStopEvent_);

		// 対象ファイルの絶対パス
		const tstring absolutePath = baseDir + finddata.cFileName;

		if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			// ディレクトリの場合、
			if (_tcscmp(finddata.cFileName, _T(".")) != 0 && _tcscmp(finddata.cFileName, _T("..")) != 0) {
				if (v_depth < settingInfo_.getMaxDepth()) {
					// サブディレクトリの深さ上限に達していなければサブディレクトリも走査する
					// (サブディレクトリの検査失敗の有無は不問)
					EnumFileRecursive(ftNow, absolutePath, v_depth + 1, v_enableEvent, numOfModified);
				}
			}
		}
		else if (!settingInfo_.isArchiveOnly() || (finddata.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) != 0) {
			// ファイルの場合、
			// ただし、アーカイブ属性のみの場合はアーカイブ属性がないものは検出しない。

			if (nameMatcher_(finddata.cFileName)) {
				// 対象ファイル名にマッチする場合、

				CFileInfoMap::iterator ite = fileInfoMap_.find(absolutePath);
				if (ite != fileInfoMap_.end()) {
					// すでにエントリに登録済みのファイル
					CFileInfo& fileInfo = ite->second;

					unsigned __int64 fileSize = CFileInfo::GetFileSize(finddata);
					if (v_enableEvent && (
							!fileTimeComparator_(finddata.ftLastWriteTime, fileInfo.getLastModified()) ||
							fileSize != fileInfo.getSize())) {
						// イベント通知ありモードで、且つ、ファイルの更新日、またはサイズが変更されている場合
						// 変更検知をセットする。(すでに確定待ちの場合は再確定待ちとなる。)
						fileInfo.setDetectModified(true);
						fileInfo.setDetectBegin(ftNow);

						// 変更通知ログ
						pLogger_->LogMessage(5, NULL, 0, IDS_LOGMES_FILEMODIFIED, absolutePath.c_str());
					}
					// ファイルの情報を更新する.
					if (fileInfo.update(finddata)) {
						numOfModified++;
					}

					// 生存していることを示す
					fileInfo.setExist(true);

					// 削除予定を解除する。
					fileInfo.setDeletePending(false);

					// 詳細ログ
					if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 9) {
						pLogger_->LogMessage(9, NULL, 0, IDS_LOGMES_UPDATEENTRY, absolutePath.c_str(), fileInfo.str().c_str());
					}
				}
				else {
					// あらたにエントリに追加されるファイル
					bool detectModified = v_enableEvent;
					CFileInfo fileInfo(finddata, detectModified, true);
					fileInfo.setDeletePending(false);
					fileInfo.setDetectBegin(ftNow);
					fileInfoMap_.insert(CFileInfoMap::value_type(absolutePath, fileInfo));
					numOfModified++;

					// 詳細ログ
					if (pLogger_->IsEnableLogging() && pLogger_->GetLogThreshold() >= 9) {
						pLogger_->LogMessage(9, NULL, 0, IDS_LOGMES_ADDENTRY, absolutePath.c_str(), fileInfo.str().c_str());
					}
					// 変更通知ログ
					if (detectModified) {
						pLogger_->LogMessage(5, NULL, 0, IDS_LOGMES_FILEMODIFIED, absolutePath.c_str());
					}
				}
			}
		}
		if ( !::FindNextFile(findHandle, &finddata)) {
			DWORD err = GetLastError();
			if (err != ERROR_NO_MORE_FILES) {
				// 次のファイルの読み取りに失敗した場合
				if (v_depth == 0) {
					// 最初のディレクトリのアクセス不可の場合、エラーとする。
					pLogger_->LogMessage(0, NULL, err,IDS_LOGMES_FINDNEXTERROR, v_baseDir.c_str());
				} else {
					// サブディレクトリのアクセス不可はログレベル3
					pLogger_->LogMessage(3, NULL, err,IDS_LOGMES_FINDNEXTERROR, v_baseDir.c_str());
				}
			}
			::FindClose(findHandle);
			break;
		}
	}

	return true;
}

/*!
 * 確定待ちのエントリが確定したかチェックし、確定した場合、アクションを実行します。
 * 一回の確定判定により複数のエントリが確定する場合、実行順序はファイルの更新日の古い順、
 * それでも同順位となる場合は絶対パス名の昇順で行われます。
 * \param ftNow 現在時刻
 * \param bSkipCommitPending 確定待ちファイル、または確定ファイルが一つもない場合にtrueに設定される。
 * \param impersonateUser ユーザの偽装オブジェクト
 * \return 確定したエントリ数
 */
int CWatchThread::CheckDoAction(const FILETIME &ftNow, bool &bSkipCommitPending, CImpersonateUser& impersonateUser)
{
	const DWORD writeWaitLimit = (DWORD) (settingInfo_.getWaitWrite() * 100);

	// 今回確定するであろうリスト
	typedef std::list<std::pair<tstring, CFileInfo*> > candidates_t;
	candidates_t candidates;

	// 再確定待ち、もしくは確定済みのファイル数
	int numOfDelected = 0;

	// アクション候補中、アクションが実行されたファイル数 (ファイルエントリの変更数と等しい。)
	int numOfCommited = 0;

	// 確定待ちエントリが確定したか判定する。
	for (CFileInfoMap::iterator ite = fileInfoMap_.begin();
		ite != fileInfoMap_.end();
		++ite) {

		// 停止イベントが発生しているか確認する。
		CWatchThreadInterruptedException::checkAndThrow(hStopEvent_);

		const tstring& absolutePath = ite->first;
		CFileInfo& fileInfo = ite->second;

		if (fileInfo.isDetectModified() && !fileInfo.isDeletePending()) {
			// 変更検知された(確定待ちの)アイテム、ただし削除予定でないこと。
			numOfDelected++;
			if (IsExpired(ftNow, writeWaitLimit, fileInfo.getDetectBegin())) {
				// ファイルサイズが落ち着くまで待機する期間を経過している場合
				unsigned __int64 nFileSize = 0;
				FILETIME lastModified = {0};
				DWORD attrs = 0;
				DWORD errcode = NO_ERROR;
				bool bAvailableFileInfo = getFileInfo(absolutePath, &nFileSize, &lastModified, &attrs);
				if ( !bAvailableFileInfo) {
					errcode = GetLastError();
				}
				if ( !bAvailableFileInfo
					|| (fileInfo.getSize() != nFileSize)
					|| !fileTimeComparator_(fileInfo.getLastModified(), lastModified)) {
					// ファイルの情報を読み取りオープンモードで取得できないか、
					// ファイルの更新日付またはサイズが変更されていれば再確定待ち
					// (属性の変更は無視する。たとえアーカイブ属性がかわったとしても次の再走査に任せる。)
					if (bAvailableFileInfo) {
						// 取得できた最新の情報で更新する。
						fileInfo.setAttributes(attrs);
						fileInfo.setSize(nFileSize);
						fileInfo.setLastModified(lastModified);
					}
					fileInfo.setDetectBegin(ftNow);
					// ログ
					if (pLogger_->GetLogThreshold() >= 9) {
						pLogger_->LogMessage(9, NULL, errcode, IDS_LOGMES_FILEMODIFY_RETRY, absolutePath.c_str());
					}
				}
				else {
					// ファイルサイズに変化がない場合は、ほぼ確定。
					// (あとはアクションが実行できるか否か。)
					candidates.push_back(candidates_t::value_type(absolutePath, &fileInfo));
				}
			}
		}
	}

	if ( !candidates.empty()) {
		// ファイルの更新日の古い順(小さい順)、ファイルの更新日が等しければ絶対パスの昇順で比較するコンパレータ
		struct PredicateWriteTime
		{
			bool operator()(const candidates_t::value_type &a, const candidates_t::value_type &b)
			{
				const tstring& absolutePath_a = a.first;
				const tstring& absolutePath_b = b.first;
				const CFileInfo* pFileInfo_a = a.second;
				const CFileInfo* pFileInfo_b = b.second;

				int ret = pFileInfo_a->compare(*pFileInfo_b);
				if (ret == 0) {
					ret = absolutePath_a.compare(absolutePath_b);
				}
				return ret < 0;
			}
		};

		// ファイルの更新日の古い順(小さい順)、ファイルの更新日が等しければ絶対パスの昇順でソートする。
		candidates.sort(PredicateWriteTime());

		// 確定候補に対して実行できるか確認しながら、実行できるならば確定してゆく。
		for (candidates_t::iterator ite = candidates.begin();
			ite != candidates.end();
			++ite) {

			// 停止イベントが発生しているか確認する。
			CWatchThreadInterruptedException::checkAndThrow(hStopEvent_);

			if ( !pActionInvoker_->canDoAction()) {
				// 同時プロセス数が限界に達したので、ここで処理終了
				break;
			}

			const tstring& absolutePath = ite->first;
			CFileInfo &fileInfo = *ite->second;

			// 実行済みとみなすため、変更検知フラグをoffする。
			fileInfo.setDetectModified(false);
			numOfCommited++;

			// 実行回数のカウントアップ
			fileInfo.incrementRunCount();
			
			// ログ
			pLogger_->LogMessage(1, NULL, 0, IDS_LOGMES_FILEMODIFY_COMMITED, absolutePath.c_str());
			
			// アクションを実行する。
			// アクション時に偽装解除が指定されていれば、偽装状態を一旦解除する。
			bool impersonateOld = bRevertToSelfOnAction_ && impersonateUser.revertToSelf();
			try {
				pActionInvoker_->doAction(absolutePath, fileInfo);
			}
			catch (...) {
				// 例外が発生した場合、偽装状態を戻してからリスローする
				if (impersonateOld) {
					impersonateUser.impersonate();
				}
				throw;
			}
			if (impersonateOld) {
				// アクション実行前に偽装されていたら、偽装を再開する。
				try {
					impersonateUser.impersonate();
				}
				catch (const CImpersonateUserException&) {
					if ( !bContinueWhenFailedImpersonate_) {
						// ログインユーザの偽装に失敗しても継続する指示がなければ
						// 例外をリスローしてスレッドを終了する。
						// そうでなければ、この例外は無視する。
						throw;
					}
				}
			}
		}
	}

	// 確定待ち、もしくは確定しているファイル数がゼロであるか?
	assert((numOfDelected - numOfCommited) >= 0 && "確定待ち、もしくは確定ファイルのカウントに誤りがあります。");
	if ((numOfDelected - numOfCommited) == 0) {
		// 次にファイルエントリが更新されないかぎりチェック不要であることを示す。
		bSkipCommitPending = true;
		if (pLogger_->GetLogThreshold() >= 10) {
			pLogger_->LogMessage(10, NULL, 0, _T("no undecided file to commit."));
		}
	}

	assert(numOfCommited >= 0 && "確定ファイル数が不正です。");
	return numOfCommited;
}

void CWatchThread::run()
{
	// ファイル検査時に使用するディレクトリパスの作成
	PropertyStringExpander expander;
	app.setAppConfVariablesTo(expander);
	tstring watchDir = expander.expandString(settingInfo_.getWatchDir());
	const tstring baseDir = EnsureLast(watchDir, _T("\\"));

	// 開始メッセージ
	pLogger_->LogMessage(
		1,
		NULL,
		0,
		IDS_LOGMES_INIT,
		baseDir.c_str(),
		settingInfo_.getMaxDepth(),
		settingInfo_.getWatchFile().c_str()
		);

	// 永続化保存要求フラグ
	bool bPersistRequired = true;
	// 強制永続化要求フラグ
	bool bForcePersist = false;

	try {
		// ユーザを偽装する。
		CImpersonateUser impersonateUser;
		impersonateUser.SetLogger(pLogger_.get());
		try {
			if (settingInfo_.isImpersonate()) {
				impersonateUser.logon(
					settingInfo_.getImpersonateUser(),
					settingInfo_.getImpersonateDomain(),
					settingInfo_.getImpersonatePassword()
					);
				// 偽装を開始する。
				impersonateUser.impersonate();
			}
		}
		catch (const CImpersonateUserException&) {
			if ( !bContinueWhenFailedImpersonate_) {
				// ログインユーザの偽装に失敗しても継続する指示がなければ
				// 例外をリスローしてスレッドを終了する。
				// そうでなければ、この例外は無視する。
				throw;
			}
		}

		FILETIME ftNow;
		::GetSystemTimeAsFileTime(&ftNow);

		FILETIME lastSweep = FILETIME();
		FILETIME lastHeapCompact = FILETIME();
		FILETIME lastForceTickCount = FILETIME();
		FILETIME lastCheck = FILETIME();
		FILETIME lastSavePersist = FILETIME();

		// 監視ハンドル
		WatchHandle watchHandle(settingInfo_);
		watchHandle.SetLogger(pLogger_.get());

		// 通知ハンドルの作成(必要なら)
		watchHandle.checkAndRenew();

		// 監視ファイルリストをロードする
		bPersistRequired = false;
		bool loaded = Load();
		if (loaded) {
			lastSavePersist = ftNow;
			// ログ (ログレベル1)
			pLogger_->LogMessage(1, NULL, 0, IDS_LOGMES_LOADPERSIST, fileInfoMap_.size());
		}

		// 起動時のリストの初期化
		// 監視ファイルリストの復元に成功した場合は前回保存時からの状態変化を
		// 監視するためイベント処理を行う。(説明としては初回存在するファイルは無視される形になる。)
		// もしくは「起動時に存在するファイルを通知する」が指定されている場合も同様。
		// それ以外は初回起動時に存在する既存ファイルをイベントなしで取得する。
		int numOfModified = EnumFile(ftNow, loaded || settingInfo_.isNotifyOnStart());
		if (numOfModified > 0 || !loaded) {
			// 監視状態が復元されていないか、変更が検知された場合は永続化する必要あり。
			bPersistRequired = true;
		}
		lastForceTickCount = ftNow;
		pLogger_->LogMessage(7, NULL, 0, IDS_LOGMES_INITLIST, numOfModified, fileInfoMap_.size());

		pLogger_->LogMessage(1, NULL, 0, IDS_LOGMES_START);

		// フォルダ走査要求フラグ
		bool bEnumerationRequired = false;

		// 削除待機中が存在しないことが確定しているフラグ
		bool bSkipDeletePending = false;
		// 確定待機中が存在しないことが確定しているフラグ
		bool bSkipCommitPending = false;

		// ファイル変更通知ループの開始
		for (;;) {
			// 現在時刻の取得
			::GetSystemTimeAsFileTime(&ftNow);

			// エントリの削除
			if ( !bSkipDeletePending && IsExpired(ftNow, spanSweepDeletedEntries_, lastSweep)) {
				int numOfBeforeEntries = fileInfoMap_.size();
				numOfModified = sweepDeletedEntries(ftNow, bSkipDeletePending);
				if (numOfModified > 0) {
					bPersistRequired = true;
					if (pLogger_->GetLogThreshold() >= 5) {
						pLogger_->LogMessage(5, NULL, 0, IDS_LOGMES_REMOVELIST, numOfModified, numOfBeforeEntries);
					}
				}
				// 現在時刻の再取得(エントリ削除処理後)
				::GetSystemTimeAsFileTime(&ftNow);
				lastSweep = ftNow;
			}

			// ヒープの圧縮(定期実行)
			if (spanHeapCompaction_ > 0 && IsExpired(ftNow, spanHeapCompaction_, lastHeapCompact)) {
				pLogger_->LogMessage(10, NULL, 0, _T("heap compaction"));
				_heapmin();
				lastHeapCompact = ftNow;
			}

			// ログライタに定期ビートを送信する。
			pLogger_->Tick(ftNow);

			// プロセスの終了待ちしているエントリから、終了済みのプロセスをクリーンナップする。
			pActionInvoker_->sweepGarbage();

			// アクションの実行
			if ( !bSkipCommitPending && CheckDoAction(ftNow, bSkipCommitPending, impersonateUser) > 0) {
				bPersistRequired = true;
			}

			// イベント待機ハンドル
			// 0: 停止イベント
			// 1: フォルダ監視ハンドル(なければNULL)
			HANDLE findHandle = (HANDLE) watchHandle;
			HANDLE handles[] = {
				hStopEvent_,
				NULL
			};
			int handleCount = 1;
			if (findHandle != INVALID_HANDLE_VALUE) {
				handles[1] = findHandle;
				handleCount = 2;
			}

			// イベントを待機する。
			// メッセージがあればbaseCycle以前に戻る可能性もある。
			const DWORD ret = ::WaitForMultipleObjects(
				handleCount,
				handles,
				FALSE,
				baseCycle_
				);
			if (ret == WAIT_OBJECT_0 || ret == WAIT_ABANDONED_0) {
				// 停止イベントが発生した。
				pLogger_->LogMessage(10, NULL, 0, _T("stop event"));
				break;
			}
			
			if (ret == WAIT_OBJECT_0 + 1 || ret == WAIT_ABANDONED_0 + 1) {
				// フォルダ監視イベントが発生した。
				bEnumerationRequired = true;

				// ログ
				pLogger_->LogMessage(3, NULL, 0, IDS_LOGMES_CHANGENOTIFY);

				// 次のフォルダ監視イベントを受理可能にする。
				if ( !::FindNextChangeNotification(findHandle)) {
					// フォルダ監視イベントの受理再開に失敗した場合
					DWORD err = GetLastError();
					pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_WATCHNEXTERROR, baseDir.c_str());

					// ハンドルを一旦無効化するが処理は継続する。
					watchHandle.Close();
				}
			}

			// 監視ハンドルを再作成する。(必要なら)
			watchHandle.checkAndRenew();

			// 強制更新検査が指定されており、最後に検査してから指定時間を経過しているため強制検査を行う。
			if (settingInfo_.getForceInterval() > 0 &&
				IsExpired(ftNow, settingInfo_.getForceInterval() * 1000L, lastForceTickCount)) {
				bEnumerationRequired = true;
				lastForceTickCount = ftNow;

				// ログ
				pLogger_->LogMessage(7, NULL, 0, IDS_LOGMES_FORCEINTERVAL);

				// 定期フォルダチェック時は無条件に永続化も行うか?
				if (bPersistAtForceInterval_) {
					bForcePersist = true;
				}
			}

			// フォルダの走査要求があれば、フォルダを走査する。
			if (bEnumerationRequired) {
				DWORD watchDelay = settingInfo_.getWatchDelay();
				if (watchDelay == 0 || IsExpired(ftNow, watchDelay, lastCheck)) {
					// ただし、連続する通知をひとつにまとめる期間が0以上であれば、
					// 最後に走査してから指定期間を経過していなければ走査しない。
					bEnumerationRequired = false;
					lastCheck = ftNow;
					numOfModified = EnumFile(ftNow, true);
					if (numOfModified > 0) {
						bPersistRequired = true;
					}
					bSkipDeletePending = false;
					bSkipCommitPending = false;
					pLogger_->LogMessage(7, NULL, 0, IDS_LOGMES_UPDATELIST, numOfModified, fileInfoMap_.size());

					// 現在時刻の再取得(走査後)
					::GetSystemTimeAsFileTime(&ftNow);
				}
			}

			// ファイルリストに変化があり、最後に保存してから指定時間経過していれば
			// 永続化を実施する。
			// (もしくは強制永続化が指示されている場合)
			if (bForcePersist ||
				(bPersistRequired && spanSavePersist_ > 0 && IsExpired(ftNow, spanSavePersist_, lastSavePersist))) {
				if (Save()) {
					// 成功した場合は永続化フラグをリセット
					bPersistRequired = false;

					// ログ (ログレベル7)
					pLogger_->LogMessage(7, NULL, 0, IDS_LOGMES_SAVEPERSIST, fileInfoMap_.size());
				}
				// (永続化に失敗しても連続再試行しないようにするため、成功の有無を問わず次のインターバルを開始する。)
				bForcePersist = false;
				lastSavePersist = ftNow;
			}
		}
	}
	catch (const CWatchThreadInterruptedException&) {
		// 停止イベントによる割り込みによる終了
		pLogger_->LogMessage(10, NULL, 0, _T("stop interrupted."));
	}
	catch (const CImpersonateUserException&) {
		assert(false && "ログインユーザの偽装でエラーが発生しました。");
		// ログインユーザの偽装に失敗した場合
		// ログは出力済みなので、ここでは終了するのみ。
	}
	catch (...) {
		pLogger_->LogMessage(0, NULL, 0, IDS_LOGMES_UNKNOWNEXCEPTION);
		throw; // リスローしてアプリケーションエラーにする。(それによりシステムに障害ログを記録させる。)
	}

	try {
		// 監視ファイルリストを保存する。
		if (bPersistRequired && Save()) {
			// ログ (ログレベル1)
			pLogger_->LogMessage(1, NULL, 0, IDS_LOGMES_SAVEPERSIST, fileInfoMap_.size());
		}

		// 監視ファイルリストを破棄する
		fileInfoMap_.clear();

		// 終了メッセージ
		pLogger_->LogMessage(
			1,
			NULL,
			0,
			IDS_LOGMES_FINISHED,
			baseDir.c_str(),
			settingInfo_.getMaxDepth(),
			settingInfo_.getWatchFile().c_str()
			);
	}
	catch (...) {
		pLogger_->LogMessage(0, NULL, 0, IDS_LOGMES_UNKNOWNEXCEPTION);
		throw; // リスローしてアプリケーションエラーにする。(それによりシステムに障害ログを記録させる。)
	}
}


unsigned __stdcall CWatchThread::ThreadProc(void* pParam)
{
	CWatchThread* me = (CWatchThread*) pParam;
	if (FAILED(::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) {
		app.showMessageBox(MB_SERVICE_NOTIFICATION | MB_ICONERROR | MB_OK, NULL, IDS_MAINPROC_COINITFAILED);
		return 0;
	}
	try {
		me->run();
	}
	catch (...) {
		::CoUninitialize();
		throw;
	}
	::CoUninitialize();
	return 0;
}



// 永続化ファイルの識別子 
#ifdef _UNICODE
// {7AFE7178-9D7D-4d2e-89A9-65EF4D078ABB}
static const GUID SERIALIZE_SIG = { 0x7afe7178, 0x9d7d, 0x4d2e, { 0x89, 0xa9, 0x65, 0xef, 0x4d, 0x7, 0x8a, 0xbb } };
#else
// {9082879D-C96D-4a01-8672-6E65681CFE7B}
static const GUID SERIALIZE_SIG = { 0x9082879d, 0xc96d, 0x4a01, { 0x86, 0x72, 0x6e, 0x65, 0x68, 0x1c, 0xfe, 0x7b } };
#endif

bool CWatchThread::Save()
{
	tstring persist = settingInfo_.getPersist();
	if (persist.empty()) {
		return false;
	}
	HANDLE hPersist = ::CreateFile(
		persist.c_str(),
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
		NULL);
	if (hPersist == INVALID_HANDLE_VALUE) {
		DWORD err = GetLastError();
		pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_SAVEPERSIST_ERROR, _T(""), persist.c_str());
		return false;
	}

	bool succeeded = false;
	try {
		CObjectWriter writer(hPersist);
		
		// アクションの状態の保存
		pActionInvoker_->Save(writer);

		// 識別子の書き込み
		writer.WriteSig(SERIALIZE_SIG);

		DWORD numOfInfos = fileInfoMap_.size();
		writer.WriteDWORD(numOfInfos);

		// 監視アイテムの保存
		DWORD count = 0;
		for (CFileInfoMap::const_iterator ite = fileInfoMap_.begin();
			ite != fileInfoMap_.end();
			++ite) {
			const tstring& absolutePath = ite->first;
			const CFileInfo& fileInfo = ite->second;

			writer.WriteSig(count++);
			writer.WriteString(absolutePath);
			fileInfo.Save(writer);
		}

		if (!::FlushFileBuffers(hPersist)) {
			DWORD err = GetLastError();
			throw CObjectReaderWriterException(err);
		}

		succeeded = true;
	}
	catch (const CObjectReaderWriterException &v_exception) {
		DWORD err = v_exception.GetErrorCode();
		pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_SAVEPERSIST_ERROR, _T(""), persist.c_str());
	}
	catch (const std::exception &v_exception) {
		const char *pMes = v_exception.what();
		CA2T mes(pMes ? pMes : "");
		pLogger_->LogMessage(0, NULL, 0, IDS_LOGMES_SAVEPERSIST_ERROR, mes, persist.c_str());
	}

	::CloseHandle(hPersist);

	return succeeded;
}

bool CWatchThread::Load()
{
	tstring persist = settingInfo_.getPersist();
	if (persist.empty()) {
		return false;
	}
	HANDLE hPersist = ::CreateFile(
		persist.c_str(),
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
		NULL);
	if (hPersist == INVALID_HANDLE_VALUE) {
		DWORD err = GetLastError();
		if (err == ERROR_FILE_NOT_FOUND || err == ERROR_ACCESS_DENIED) {
			pLogger_->LogMessage(1, NULL, err, IDS_LOGMES_PERSIST_NOT_FOUND, persist.c_str());
		}
		else {
			pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_LOADPERSIST_ERROR, _T(""), persist.c_str());
		}
		return false;
	}

	ULARGE_INTEGER siz = {0};
	siz.LowPart = ::GetFileSize(hPersist, &siz.HighPart);
	DWORD err = GetLastError();
	if ((siz.LowPart == -1 && err != NO_ERROR) || siz.QuadPart == 0) {
		// ファイルがないか、ファイルのサイズ読み取りに失敗した場合
		if (err != NO_ERROR) {
			// エラーの場合はログに出す。
			pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_LOADPERSIST_ERROR, _T(""), persist.c_str());
		}
		::CloseHandle(hPersist);
		return false;
	}

	bool succeeded = false;
	try {
		CObjectReader reader(hPersist);
		
		// アクションの状態の復元
		pActionInvoker_->Load(reader);

		// 識別子の検証
		reader.VerifySig(SERIALIZE_SIG);

		// 現在の情報のクリア
		fileInfoMap_.clear();

		// 保存されているアイテム数の取得
		DWORD numOfInfos = reader.ReadDWORD();

		// 監視アイテムのロード
		for (DWORD count = 0; count < numOfInfos; count++) {

			// 停止イベントが発生しているか確認する。
			CWatchThreadInterruptedException::checkAndThrow(hStopEvent_);

			// エントリの開始マークの確認
			reader.VerifySig(count);

			tstring absolutePath;
			reader.ReadString(absolutePath);

			CFileInfo fileInfo;
			fileInfo.Load(reader);

			// 削除待機中でなければ実在フラグをたてる。
			// (ディレクトリ走査中は実在フラグは一旦リセット状態にされるため
			// その期間中に停止した場合は実在フラグは曖昧となる。)
			if ( !fileInfo.isDeletePending()) {
				fileInfo.setExist(true);
			}

			fileInfoMap_.insert(CFileInfoMap::value_type(absolutePath, fileInfo));
		}

		// 実行回数をリセットする。(必要ならば。)
		if (nResetRunCount_ > 0) {
			pActionInvoker_->Reset();
		}
		if (nResetRunCount_ > 1) {
			for (CFileInfoMap::iterator ite = fileInfoMap_.begin();
				ite != fileInfoMap_.end();
				++ite) {
				CFileInfo& fileInfo = ite->second;
				fileInfo.setRunCount(0);
			}
		}

		succeeded = true;
	}
	catch (const CWatchThreadInterruptedException&) {
		// 停止イベントによる割り込みによる中断
		// リスローして呼び出しもとで処理する。
		throw;
	}
	catch (const CObjectReaderWriterException &v_exception) {
		DWORD err = v_exception.GetErrorCode();
		pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_LOADPERSIST_ERROR, _T(""), persist.c_str());
	}
	catch (const std::exception &v_exception) {
		const char *pMes = v_exception.what();
		CA2T mes(pMes ? pMes : "");
		pLogger_->LogMessage(0, NULL, 0, IDS_LOGMES_LOADPERSIST_ERROR, mes, persist.c_str());
	}

	::CloseHandle(hPersist);

	if ( !succeeded) {
		// エラーが発生した場合は不完全な状態の可能性があるため、
		// 復元状態をリセットする。
		fileInfoMap_.clear();
	}

	return succeeded;
}
