/*

MercuryInstaller for MS-DOS
インストール&QQQファイル処理ルーチン

*/

#include<ctype.h>
#include<direct.h>
#include<dos.h>
#include<farstr.h>
#include<fcntl.h>
#include<io.h>
#include<jstring.h>
#include<setjmp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>

#include"mercury.h"

/*****************************************************************************/
/*                              グローバル変数                               */
/*****************************************************************************/
static	char	Sourcedir[128] = "";	/* コピー元のディレクトリ  */
static	int	Qqqlevel = 0;		/* QQQファイルの再帰レベル */
/*****************************************************************************/
/*                               下請け処理                                  */
/*****************************************************************************/
/*-----------------------------コピー元パスの作成----------------------------*/
/* 指定されたパスが相対パスによるものであれば、それにドライブ名・パス名をつけ*/
/* 加える                                                                    */
/*---------------------------------------------------------------------------*/
static	void	mksourcepath(char *d,char *s)
{
	if	(s[1]==':')			/* ドライブ名から指定       */
		strcpy(d,s);
	else if	(s[0]=='\\')			/* 標準ﾄﾞﾗｲﾌﾞのﾙｰﾄから指定  */
		sprintf(d,"%c:%s",Drive,s);
	else					/* 標準ディレクトリから指定 */
		sprintf(d,"%c:%s\\%s",Drive,Sourcedir,s);

	if	(Sourcedir[0]=='\0')
	{
		char	*p;

		if	(s[1]==':')
			strcpy(Sourcedir,s+2);
		else if	(s[0]=='\\')
			strcpy(Sourcedir,s);

		p = jstrrchr(Sourcedir,'\\');
		if	(p!=NULL)
			*p = '\0';
	}
}
/*---------------ディレクトリが存在するかどうかチェック----------------------*/
static	int	is_directory_exist(char *path)
{
	char	buf[128];

	if	(getcwd(buf,sizeof(buf))==NULL)
		return 0;

	if	(chdir(path)!=0)
		return 0;

	chdir(buf);
	return 1;
}
/*------------------------パス名の直後の位置を探す---------------------------*/
static	char	*afterpath(char *s)
{
	char	*p = jstrrchr(s,'\\');

	if	(p!=NULL)
		return p+1;
	else if	(s[1]==':')
		return s+2;
	else
		return s;
}
/*----------------------パス名の最後に'\\'をつけ加える-----------------------*/
/* 関数の値としては'\\'をつけ加えた後の位置を返す。                          */
/*---------------------------------------------------------------------------*/
static	char	*addbackslash(char *s)
{
	char	*p = strchr(s,'\0');

	if	(p!=s && p[-1]!='\\' && p[-1]!=':')
	{
		p[0] = '\\';
		p[1] = '\0';
		return p+1;
	}
	else
		return p;
}
/*****************************************************************************/
/*                               QQQファイル                                 */
/*****************************************************************************/
/*-----------------各コマンド処理ルーチンのプロトタイプ宣言------------------*/
/* 本当は私、static関数のプロトタイプ宣言は嫌いなんですが...(^_^;)こうしない */
/* と、ifコマンドの処理で間接再帰を使うのでうまく書けないのです(;_;)。       */
/*---------------------------------------------------------------------------*/
static	int	cmd_dummy(char *);
static	int	cmd_chdir(char *);
static	int	cmd_copy(char *);
static	int	cmd_exit(char *);
static	int	cmd_if(char *);
static	int	cmd_inst(char *);
static	int	cmd_look(char *);
static	int	cmd_mkdir(char *);
static	int	cmd_pause(char *);
static	int	cmd_rename(char *);
static	int	cmd_xcopy(char *);
/*-------------------------------型と変数------------------------------------*/
static	struct	COMMANDS
{
	char	*str;
	int	(*func)(char *);
} Commands[] = 
{
	{"chdir" ,cmd_chdir },
	{"copy"  ,cmd_copy  },
	{"else"  ,cmd_dummy },
	{"endif" ,cmd_dummy },
	{"exit"  ,cmd_exit  },
	{"if"    ,cmd_if    },
	{"inst"  ,cmd_inst  },
	{"look"  ,cmd_look  },
	{"mkdir" ,cmd_mkdir },
	{"pause" ,cmd_pause },
	{"play"  ,cmd_dummy },
	{"rename",cmd_rename},
	{"xcopy" ,cmd_xcopy },
};

typedef	enum
{
	CMD_CHDIR,CMD_COPY,CMD_ELSE,CMD_ENDIF,CMD_EXIT,CMD_IF,CMD_INST,
	CMD_LOOK,CMD_MKDIR,CMD_PAUSE,CMD_PLAY,CMD_RENAME,CMD_XCOPY,
	CMD_EOF,
} COMMANDID;

jmp_buf	Jmp_buf;
/*----------------------QQQファイルのアクセスルーチン------------------------*/
/* qqq_fgetsは、*argに内部のstaticバッファへのポインタを返す。従って、次に呼 */
/* ばれるまでその内容は有効。                                                */
/*---------------------------------------------------------------------------*/
static	FILE	*Qqqfile[FOPEN_MAX+1];	/* 1..FOPEN_MAX */

static	int	qqq_fopen(char *filename)
{
	char	buf[128];
	FILE	*fp;

	mksourcepath(buf,filename);

	fp = fopen(buf,"r");
	if	(fp==NULL)
	{
		putmessage("%sがオープンできません",buf);
		return 0;
	}

	Qqqfile[++Qqqlevel] = fp;
	return 1;
}

static	void	qqq_fclose(void)
{
	fclose(Qqqfile[Qqqlevel--]);
}

static	COMMANDID	qqq_fgets(char **arg)
{
	static	char	buf[512];
	char		*p,*q;
	int		i;

rep:
	if	(Qqqlevel==0)
		return CMD_EOF;

	if	(fgets(buf,sizeof(buf),Qqqfile[Qqqlevel])==NULL)
	{
		qqq_fclose();
		goto rep;
	}

	if	((p=strchr(buf,'\n'))!=NULL)
		*p = '\0';

	p = buf;
	while	(isspace(*p))
		p++;

	if	(*p=='#' || *p=='\0')
		goto rep;

	q = p;

	while	(isalpha(*p))
		p++;
	*p = '\0';

	do
		p++;
	while	(isspace(*p));
	*arg = p;

	for	(i=0 ; i<MEMBERSOF(Commands) ; i++)
	{
		if	(stricmp(q,Commands[i].str)==0)
			return (COMMANDID)i;
	}

	putmessage("QQQファイルに不正な命令があります:%s",q);
	longjmp(Jmp_buf,-1);
}

static	void	qqq_release(void)
{
	while	(Qqqlevel)
		qqq_fclose();
}
/*-----------------QQQファイル解釈ルーチンのメインルーチン-------------------*/
static	int	qqqinterpreter(char *filename)
{
	char		*p;
	int		a;
	COMMANDID	id;

	if	(!qqq_fopen(filename))
		return 0;

	a = setjmp(Jmp_buf);
	if	(a!=0)
	{
		qqq_release();
		if	(a<0)	return 0;
		else		return 1;
	}

	while	((id=qqq_fgets(&p))!=CMD_EOF)
	{
		if	(!Commands[id].func(p))
		{
			qqq_release();
			return 0;
		}
	}

	qqq_release();

	return 1;
}
/*****************************************************************************/
/*                        QQQファイルの各コマンドの処理                      */
/*****************************************************************************/
/*-----------------------------ダミーコマンド--------------------------------*/
static	int	cmd_dummy(char *s)
{
	return 1;
}
/*------------------------------chdirコマンド--------------------------------*/
static	int	cmd_chdir(char *s)
{
	if	(chdir(s)==0)
		return 1;

	putmessage("ディレクトリ%sがありません");
	return 0;
}
/*-------------------------------copyコマンド--------------------------------*/
static	int	cmd_copy(char *s)
{
	char	*p = s;

	static	int	copy(int f,char *s,char *d);

	while	(!isspace(*p))
		p++;

	if	(*p!='\0')
	{
		*p++ = '\0';
		while	(isspace(*p))
			p++;
	}

	return copy(0,s,p);
}
/*------------------------------exitコマンド---------------------------------*/
static	int	cmd_exit(char *s)
{
	longjmp(Jmp_buf,1);
}
/*-------------------------------ifコマンド----------------------------------*/
static	int	cmd_if(char *mes)
{
	COMMANDID	id;
	char		*p;
	int		c;

	c = window_select(mes,"Y:はい","N:いいえ",NULL);

	if	(c=='Y' || c=='y')
	{
		while	((id=qqq_fgets(&p))!=CMD_ELSE &&
				id!=CMD_ENDIF && id!=CMD_EOF)
		{
			if	(!Commands[id].func(p))
				return 0;
		}

		if	(id==CMD_ELSE)
		{
			while	((id=qqq_fgets(&p))!=CMD_ENDIF && id!=CMD_EOF)
				;
		}
	}
	else
	{
		while	((id=qqq_fgets(&p))!=CMD_ELSE &&
				id!=CMD_ENDIF && id!=CMD_EOF)
			;

		if	(id==CMD_ELSE)
		{
			while	((id=qqq_fgets(&p))!=CMD_ENDIF && id!=CMD_EOF)
			{
				if	(!Commands[id].func(p))
					return 0;
			}
		}
	}

	return 1;
}
/*-------------------------------instコマンド--------------------------------*/
static	int	cmd_inst(char *s)
{
	return qqq_fopen(s);
}
/*-------------------------------lookコマンド--------------------------------*/
/* Helperとの互換性のため一応ダミーを用意しておく                            */
/*---------------------------------------------------------------------------*/
static	int	cmd_look(char *s)
{
	openwindow(2,64);
	window_putstr(0,"LOOK:\n%s",s);
	ds_getch();
	closewindow();
	return 1;
}
/*------------------------------mkdirコマンド--------------------------------*/
static	int	cmd_mkdir(char *s)
{
	if	(mkdir(s)==0)
		return 1;

	putmessage("ディレクトリ%sが作れません");
	return 0;
}
/*-------------------------------pauseコマンド-------------------------------*/
static	int	cmd_pause(char *s)
{
	openwindow(1,strlen(s)+1);
	window_putstr(0,"%s",s);
	ds_getch();
	closewindow();
	return 1;
}
/*------------------------------renameコマンド-------------------------------*/
static	int	cmd_rename(char *s)
{
	char	*p = s;

	while	(!isspace(*p))
		p++;

	if	(*p=='\0')
	{
		putmessage("RENAMEコマンドの書式が不正です");
		return 0;
	}

	*p = '\0';
	while	(isspace(*p))
		p++;

	if	(rename(s,p)==0)
		return 1;

	putmessage("RENAMEできませんでした");
	return 0;
}
/*------------------------------xcopyコマンド--------------------------------*/
static	int	cmd_xcopy(char *s)
{
	char	*p = s;

	static	int	copy(int f,char *s,char *d);

	while	(!isspace(*p))
		p++;

	if	(*p!='\0')
	{
		*p++ = '\0';
		while	(isspace(*p))
			p++;
	}

	return copy(1,s,p);
}
/*****************************************************************************/
/*                             ファイルのコピー                              */
/*****************************************************************************/
/*------------------------単一ファイルのコピー-------------------------------*/
/*  次の要件を満たすとき、srcをQQQファイルとみなしてqqqinterpreter()を呼び出 */
/* す。                                                                      */
/*    ○srcが示すのがQQQファイルである                                       */
/*    ○このcopy_onefile()がQQQファイルの(X)COPY命令から呼ばれたものでない   */
/*      すなわち、QQQlevel==0                                                */
/*---------------------------------------------------------------------------*/
/* なぜか高水準入出力を使うと極端にパフォーマンスが落ちるので、あえて低水準  */
/* 入出力を使用している                                                      */
/*---------------------------------------------------------------------------*/
static	int	copy_onefile(char *src,char *dst)
{
	int	fdi=-1,fdo=-1;
	long	size,size2;
	int	n;

	char	buf[4096];

	if	(Qqqlevel==0)
	{
		char	*p = strchr(src,'\0');

		if	(p>src+4 && stricmp(p-4,".qqq")==0)
			return qqqinterpreter(src);
	}

	openwindow(4,64);
	diet_setmode(0);

	fdi=open(src,O_RDONLY);
	if	(fdi==-1)
	{
		window_putstr(0,"%sがオープンできません",src);
		ds_getch();
		goto error;
	}

	fdo=open(dst,O_WRONLY|O_TRUNC|O_CREAT,S_IREAD|S_IWRITE);
	if	(fdo==-1)
	{
		window_putstr(0,"%sがオープンできません",dst);
		ds_getch();
		goto error;
	}

	size  = filelength(fdi);
	size2 = 0;
	window_putstr(0,"%s\n",dst);
	window_putstr(3,"[ESC]/[F10]で中止します");

	{
		unsigned	date,time;

		_dos_getftime(fdi,&date,&time);
		_dos_setftime(fdo,date,time);
	}

	while	((n=read(fdi,buf,sizeof(buf)))>0) /* エラー処理は少々手抜き */
	{
		if	(write(fdo,buf,n)==-1)
			goto error;

		size2 += n;
		window_putstr(2,"%ld/%ld",size2,size);

		if	(ds_kbhit())
		{
			int	c = ds_getch();

			if	(c==FKEY_ESC || c==FKEY_F10)
			{
				closewindow();
				openwindow(1,14);
				window_putstr(0,"中止しました.");
				goto error;
			}
		}
	}

	close(fdi);
	close(fdo);
	closewindow();
	diet_setmode(1);
	return 1;

error:
	if	(fdi!=NULL)	close(fdi);
	if	(fdo!=NULL)	close(fdo);

	closewindow();
	diet_setmode(1);
	return 0;
}
/*--------------------ファイルを検索して線形リストに格納---------------------*/
struct	DIRCHAIN
{
	char		name[13];
	char		attr;
	unsigned	time;
	unsigned	date;
	unsigned long	size;
	struct DIRCHAIN	*next;
};

struct DIRCHAIN	*ds_opndir(char *path, char attr)
{
	char		*chp;
	struct find_t	fib;
	struct DIRCHAIN	*dirtop;
	struct DIRCHAIN	*dir;
	static char	buf[128];

	strncpy(buf, path, sizeof(buf)-1);
	chp = strchr(buf,'\0') -1;

	if (*chp==':' || *chp=='\\')
		strncat(buf, "*.*", sizeof(buf) - 1);

	if	(_dos_findfirst(buf, (unsigned)attr, &fib))
		return NULL;
	if ((dir = malloc(sizeof(struct DIRCHAIN))) == NULL)
		return NULL;
	dirtop = dir;
	strcpy(dir->name, fib.name);
	dir->attr = fib.attrib;
	dir->time = fib.wr_time;
	dir->date = fib.wr_date;
	dir->size = fib.size;
	dir->next = NULL;
	while (_dos_findnext(&fib) == 0)
	{
		if ((dir->next = malloc(sizeof(struct DIRCHAIN))) == NULL)
			return NULL;
		dir = dir->next;
		strcpy(dir->name, fib.name);
		dir->attr = fib.attrib;
		dir->time = fib.wr_time;
		dir->date = fib.wr_date;
		dir->size = fib.size;
		dir->next = NULL;
	}
	return dirtop;
}
/*-----------------単一ファイル→単一ファイルのコピーか?---------------------*/
static	int	is_single_to_single(char *src,char *dst)
{
	char	*p;

	if	(jstrchr(src,'*')!=NULL || jstrchr(src,'?')!=NULL)
		return 0;

	p = strchr(dst,'\0');
	if	(p==dst || p[-1]=='\\' || p[-1]==':')
		return 0;

	if	(is_directory_exist(dst))
		return 0;

	if	(access(src,4)==0)
		return 1;
	else
		return 0;
}
/*----------------指定されたパスの親ディレクトリがなければ作る---------------*/
static	int	mkparentdir(char *path)
{
	char	*p;
	int	n = 0;

	while	((p=jstrrchr(path,'\\'))!=NULL)
	{
		*p = '\0';
		n++;
		if	(chdir(path)==0)
			break;
	}

	for	( ; n ; *strchr(path,'\0')='\\',n--)
	{
		char	buf[128];
		int	c;

		if	(chdir(path)==0)
			continue;
		if	(path[0]=='\0' || (path[1]==':' && path[2]=='\0'))
			continue;

		strcpy(buf,path);
		strcat(buf,"\nディレクトリが存在しません 作りますか?");

		c = window_select(buf,"Y:はい","N:いいえ",NULL);
		if	(c=='N' || c=='n')
			goto error;

		if	(mkdir(path)!=0)
		{
			putmessage("ディレクトリ%sが作れません",path);
			goto error;
		}
	}
	return 1;

error:
	while	(n--)
		*strchr(path,'\0') = '\\';

	return 0;
}
/*-------------------------COPY/XCOPYの下請け再帰関数------------------------*/
/* srcとdstの内容は破壊されるので注意                                        */
/*---------------------------------------------------------------------------*/
recursive static int	xcopy_sub(int isxcopy,char *src,char *dst)
{
	char		*s = afterpath(src);
	char		*d = afterpath(dst);
	struct DIRCHAIN	*dir = ds_opndir(src,_A_NORMAL|_A_SUBDIR);

	while	(dir!=NULL)
	{
		struct DIRCHAIN	*temp;

		if	(dir->name[0]=='.')
			goto next;

		strcpy(s,dir->name);
		strcpy(d,dir->name);

		if	(dir->attr & _A_SUBDIR)
		{
			if	(!isxcopy)
			{
				char	buf[128];
				int	c;

				sprintf(buf,"%sディレクトリもコピーしますか?",
								dir->name);

				c=window_select(buf,"Y:はい","N:いいえ",NULL);
				if	(c=='N' || c=='n')
					goto next;
			}

			if	(!is_directory_exist(dst) && mkdir(dst))
			{
				putmessage("ディレクトリ%sが作れません",dst);
				goto error;
			}

			strcat(s,"\\*.*");
			strcat(d,"\\");
			if	(!xcopy_sub(isxcopy,src,dst))
				goto error;
		}
		else
		{
			if	(!copy_onefile(src,dst))
				goto error;
		}
	next:
		temp = dir;
		free(dir);
		dir = temp->next;
	}

	return 1;

error:
	while	(dir!=NULL)
	{
		struct DIRCHAIN	*temp = dir;

		free(dir);
		dir = temp->next;
	}

	return 0;
}
/*---------------------------コピーのメインルーチン--------------------------*/
static	int	copy(int isxcopy,char *src,char *dst)
{
	char	src2[128];
	char	dst2[128];

	mksourcepath(src2,src);
	strcpy(dst2,dst);

	if	(is_single_to_single(src2,dst2))
	{
		if	(!mkparentdir(dst2))
			return 0;
		else
			return copy_onefile(src2,dst2);
	}

	addbackslash(dst2);

	if	(!mkparentdir(dst2))
		return 0;

	return xcopy_sub(isxcopy,src2,dst2);
}
/*****************************************************************************/
/*                         インストーラのメインルーチン                      */
/*****************************************************************************/
extern	void	installer(struct DATA far *data)
{
	static	char		target[128] = "";
	char			buf[128];
	struct	COPYDATA_T far	*p;
	char			*s;

	strcpy(buf,target);
	if	(!window_strinput("インストール先のディレクトリを指定してください",buf,64))
		return;

	if	(data->dir!=NULL)
		far_strcpy(Sourcedir,data->dir);
	else
		Sourcedir[0] = '\0';	/* qqqinterpreter()で設定する? */

	strcpy(target,buf);
	s = addbackslash(buf) - 1;
	if	(!mkparentdir(buf))
		return;

	if	(s!=buf && s[-1]!=':')
		*s = '\0';

	chdir(buf);
	if	(buf[1]==':')
		bdos(0x0e,toupper(buf[0])-'A',0);

	if	(data->copy==NULL)
	{
		copy(0,"*.*","");
		return;
	}

	for	(p=data->copy ; p!=NULL ; p=p->next)
	{
		far_strcpy(buf,p->string);

		if	(!copy(p->isxcopy,buf,""))
			return;
	}
}
/*-----------------------------End of install.c------------------------------*/
