/*
 *	app.c
 *
 *	FC_COPY.exe - ファイル内容を比較して一致しない場合のみコピーするツール
 *	Copyright (C) 2015-2017 Naoyuki Sawa
 *
 *	* Wed Nov 11 21:51:30 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- これまで使用していたFC_COPY.batと、ほぼ同じ機能を持つexeとして再実装しました。
 *	  FC_COPY.batを置き換える事にした理由は、バッチよりもexeの方が安定動作が期待出来るからです。
 *	  FC_COPY.batもこれまで問題なく使用出来ていたので、./keep/FC_COPY.bat に保存しておきました。
 *	  FC_COPY.batはバッチの可変引数のサンプルとして参考になると思うので、今後も必要に応じて参照して下さい。
 *	- FC_COPY.batとの、動作上の相違点は、以下の通りです。
 *	  ①FC_COPY.batは、コピーを実行した場合は、コピー先ファイルのタイムスタンプは、コピー元ファイルのタイムスタンプと同じになっていました。
 *	  │FC_COPY.exeでは、コピーを実行した場合は、コピー先ファイルのタイムスタンプは、現在の時刻になります。
 *	  │尚、もしFC_COPY.exeでもFC_COPY.batと同じ動作にしたい場合は、CopyFile()APIを使えば良いのですが、必要無いと思うので対応していません。
 *	  │コピーを実行した後に確実にビルドが走るようにする事を考えると、今回作成したFC_COPY.exeの動作の方が適切だと思います。
 *	  ②FC_COPY.batは、コピー元ファイル名を最大8個までしか指定出来ません。
 *	  │FC_COPY.exeでは、SETARGV.OBJが展開出来る限り、コピー元ファイル名の指定数に制限は有りません。
 *	- ちなみに、CMake(http://cmake.org/)を利用して、「cmake -E copy_if_different in-file out-file」でも同様の事が出来るのですけれど、
 *	  P/ECEを使う環境にCMakeがインストールされているとは限らないし、FC_COPY.exeのコマンド書式の方が複数指定が出来て便利だと思います。
 *	* Sun Feb 21 21:02:01 JST 2016 Naoyuki Sawa
 *	- libclipのライブラリ化の伴い、以下の変更を行いました。
 *	  ・extclip.hとextclip.cを削除しました。extclip.hとextclip.cはlibclipに含まれるようになったからです。
 *	  ・"CLiP Files"を削除しました。CLiPライブラリの各モジュールはlibclipに含まれるようになったからです。
 *	  当ツールの動作には変更有りません。
 *	* Fri Sep 16 21:30:10 JST 2016 Naoyuki Sawa
 *	- 前回の更新以降～本日までの間に、libclipが依存するインクルードパスやライブラリパスの変更によって、当プロジェクトがビルド出来なくなっていました。
 *	  当プロジェクトにもそれらを反映して、ビルド出来るようにしました。
 *	- '-q'オプションが正しく処理出来ていなかったので、修正しました。
 *	  具体的には、subr()関数の中で、「case 'q'」の処理は有るのに、getopt()に対して"q"の指定が抜けていました。
 *	  今回、getopt()に対しても"q"を追加して、'q'オプションが正しく処理出来るように修正しました。
 *	* Mon May 08 08:35:51 JST 2017 Naoyuki Sawa
 *	- ライブラリのバグ修正に伴う、ツールのリビルドを行いました。
 *	  詳細は、clipmisc.cの'Sun May 07 09:13:01 JST 2017 Naoyuki Sawa'のコメントを参照して下さい。
 */
#include "app.h"
#define VERSION		"20170508"	//最終更新日
/****************************************************************************
 *	
 ****************************************************************************/
__declspec(noreturn) static void usage();
static int subr(int argc, char* argv[]);
/****************************************************************************
 *	main
 ****************************************************************************/
int main(int argc, char* argv[]) {
	int exitCode;
#ifdef  GC_H
//	putenv("GC_LOG_FILE=CON");//ログ出力先を指定する。
//	GC_set_find_leak(1);//メモリリーク検出を開始する。
#endif//GC_H
//{{2017/05/08削除:GC_init(),及び,GC_allow_register_threads()の呼び出しを削除しました。GC_init(),及び,GC_allow_register_threads()は、だいぶん前から、必要に応じてライブラリ内のextclip.cのGC_threads_init()で行われるようになっているので、アプリケーション側での明示的な呼び出しはもう不要です。
//#if     (defined(GC_H) && defined(PTHREAD_H))
//	GC_init();//この中でGCスレッドが起動する。メインスレッドをpthread_exit()等で終了するとGCスレッドが残ってプロセスが終了しないので注意せよ。
//	GC_allow_register_threads();
//#endif//(defined(GC_H) && defined(PTHREAD_H))
//}}2017/05/08削除:GC_init(),及び,GC_allow_register_threads()の呼び出しを削除しました。GC_init(),及び,GC_allow_register_threads()は、だいぶん前から、必要に応じてライブラリ内のextclip.cのGC_threads_init()で行われるようになっているので、アプリケーション側での明示的な呼び出しはもう不要です。
#ifdef  __CLIP_SEH_H__
	SEH_init();
#endif//__CLIP_SEH_H__
#ifdef  __CLIP_REG_H__
//	if(!(TBL_RegTbl = LockResource(LoadResource(NULL, FindResource(NULL, "RegTbl", "RegTbl"))))) { DIE(); }
#endif//__CLIP_REG_H__
#ifdef  __CLIP_PRP_H__
//	if(!(pPrpKey = REG_open_key(TBL_RegTbl, RegKey_PrpTbl))) { DIE(); }
//	PrpHdr_init(phApp, pPrpKey, PrpTbl_App);
#endif//__CLIP_PRP_H__
	exitCode = subr(argc, argv);
#ifdef  GC_H
//	CHECK_LEAKS();//メモリリークを検出する。
#endif//GC_H
//	cputs("ok");
//	getch();
	return exitCode;
}
/****************************************************************************
 *	usage
 ****************************************************************************/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "FC_COPY.exe - ファイル内容を比較して一致しない場合のみコピーするツール (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  FC_COPY.exe [option ...] srcfile1 [srcfile2 ...] dstdir\n");
	fprintf(stderr, "  ・ファイル名は、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -q  進捗表示を抑制します。\n");
	fprintf(stderr, "  -h  使い方を表示します。\n");
	exit(EXIT_FAILURE);
}
/****************************************************************************
 *	subr
 ****************************************************************************/
#define BUFFER_SIZE	(1024*1024)	//バッファサイズ(調整可)
static unsigned char srcBuf[BUFFER_SIZE], dstBuf[BUFFER_SIZE];
/*--------------------------------------------------------------------------*/
static int subr(int argc, char* argv[]) {
	int opt_q = 0;
	{
		int opt;
		opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
		while((opt = getopt(argc, argv, "qh")) != -1) {	//コマンドラインオプションを処理する。
			switch(opt) {
			case 'q':
				opt_q = 1;
				break;
			case 'h':
			default:
				usage();
			}
		}
	}
	{
		char srcFname[MAX_FNAME], srcExt[MAX_EXT], dstPath[MAX_PATH];
		char *srcPath, *dstDir;
		struct stat st;
		FILE *fpSrc, *fpDst;
		//少なくとも1個以上の各コピー元ファイルと、1個のコピー先ディレクトリが必要である。
		if(optind >= (argc - 1)) { usage(); }
		//コピー先ディレクトリが存在する事を確認する。
		dstDir = argv[argc - 1];
//{{サービス
		//ディレクトリ名の末尾に'\\'が付いていると、stat()がエラーを返すようだ。(VC++6.0にて確認)
		//それだけの原因で「コピー先ディレクトリ'～\'が存在しません。」というエラーを表示すると、ユーザーがなぜエラーなのか判らずに混乱すると思うので、
		//サービスとして、当ツール内の処理で、ディレクトリ名の末尾の'\'を確実に削除する事にした。
		dstDir = strtrim(dstDir, 2/*右端にある指定文字を除去する*/, "\\");
//}}サービス
		if(stat(dstDir, &st) || !(st.st_mode & S_IFDIR)) { error(EXIT_FAILURE, 0, "コピー先ディレクトリ'%s'が存在しません。", dstDir); }
		//各コピー元ファイルについて…
		do {
			//コピー元ファイルを開く。
			srcPath = argv[optind];
			fpSrc = fopen(srcPath, "rb");	//バイナリモード
			if(!fpSrc) { error(EXIT_FAILURE, errno, "コピー元ファイル'%s'が開けません。", srcPath); }
			//コピー先ファイルのフルパス名を作成する。
			splitpath(srcPath, NULL, NULL, srcFname, srcExt);
			makepath(dstPath, NULL, dstDir, srcFname, srcExt);
			//コピー先ファイルを読み取りモードで開いてみる。
			fpDst = fopen(dstPath, "rb");	//バイナリモード
			//コピー先ファイルが存在すなるならば…
			if(fpDst) {
				//コピー元ファイルとコピー先ファイルを比較し、完全に一致したら、コピー処理を飛ばす。
				for(;;) {
					int srcLen = fread(srcBuf, 1, BUFFER_SIZE, fpSrc);
					int dstLen = fread(dstBuf, 1, BUFFER_SIZE, fpDst);
					if((srcLen != dstLen) || memcmp(srcBuf, dstBuf, srcLen)) { break; }	//不一致
					if(srcLen < BUFFER_SIZE) {	//一致
						if(!opt_q) { printf("skip %s\n", srcPath); }	//進捗表示
						goto L_NEXT;
					}
				}
				//この下でコピー先ファイルを書き込みモードで開くので、その前にコピー先ファイルの読み取りモードを閉じておく。
				fclose(fpDst);
			}
			//コピー元ファイルを巻き戻す。
			rewind(fpSrc);
			//コピー先ファイルを書き込みモードで開く。
			fpDst = fopen(dstPath, "wb");	//バイナリモード
			if(!fpDst) { error(EXIT_FAILURE, errno, "コピー先ファイル'%s'が作成出来ません。", dstPath); }
			//コピー元ファイルをコピー先ファイルにコピーする。
			if(!opt_q) { printf("copy %s\n", srcPath); }	//進捗表示
			for(;;) {
				int srcLen = fread( srcBuf, 1, BUFFER_SIZE, fpSrc);
				int dstLen = fwrite(srcBuf, 1,      srcLen, fpDst);
				if(srcLen != dstLen) { DIE(); }	//容量不足,又は,IOエラー。通常は起こり得ない。
				if(srcLen < BUFFER_SIZE) { break; }
			}
L_NEXT://コピー処理を飛ばす場合のジャンプ先
			//コピー処理を飛ばした場合は、コピー先ファイルの読み取りモードを閉じる。
			//コピー処理を実行した場合は、コピー先ファイルの書き込みモードを閉じる。
			fclose(fpDst);
			//コピー元ファイルを閉じる。
			fclose(fpSrc);
		} while(++optind < (argc - 1));
	}
	return EXIT_SUCCESS;
}
