#!/usr/bin/perl

use strict;
use File::Basename;
use Socket;
use POSIX 'setsid';
use threads; 
use threads::shared;
use Cwd;

use lib 'd:/Programs/Perl/lib';
use lib "$ENV{TkPerlDir}/lib";

use Utils;
use MyApplication;

#===============================================
# スクリプト大域変数
#===============================================
my $DaemonMode         = 1;
my $RedirectIO         = 1;
my $TrapSignals        = 1;
my $UseThreadForChild  = 0;
my $AllowReboot        = 1;
my $AllowTerminate     = 1;

my $ServerPort         = 7798;
my $Perl               = ($^O eq 'MSWin32')? "c:/Perl/bin/perl.exe" : '/usr/bin/perl';

#アクセス制限の有無
my $IsLimitAccess = 1;
#許可するIPアドレス
my @AllowedIPAddresses =
	(
	 "^127\.0\.0\.1\$",
	 "^192\.168\.1\.\\d{1,3}\$",
	 "^192\.168\.11\.\\d{1,4}\$",
	 "^192\.168\.10\.\\d{1,4}\$",
	 "^131\.112\.130\.56\$"
	);

my %Vars : shared = (
	status              => 'run',
	RealStatus          => 'unknown',
	terminal            => 'windows',
	OneLineResponse     => 0,

	BatchLoopInterval   => 3, # sec
	CommandLoopInterval => 2, # sec

	BatchMode           => 'Dir', # CIF|Dir
	ExecuteBaseDir      => '.',
	QueueDir1           => './queue1',
	QueueDir2           => './queue2',
	QueueDir3           => './queue3',

	ScriptFileName      => "DoVASP.sh",
	ScriptFileMask      => "DoVASP*.sh",
	ScriptTemplateName  => "DoVASP-Template.sh",

	SkipFiles           => "(\\.moved|\\.finished)\$",
	SortBy              => "time",

	LogFilePath             => "Test.log",
	WriteNoActiveTargetLog  => 1,
	WriteNoActiveTargetLog2 => 1,
	LocalPrintLevel     => 1,
	);
my $LF = "\r\n";
&SetLF($Vars{terminal});

my $BatchThread;
my $CommandThread;

#==========================================
# メインルーチン
#==========================================
my $App = new MyApplication;
exit if($App->Initialize() < 0);

my $ProgramPath  = $App->SpeculateProgramPath($0, "");
my $IniFile      = $App->OpenIniFile($ProgramPath);
my $IniFilePath  = $IniFile->IniFile();
print "PP: $ProgramPath\n";
print "IP: $IniFilePath\n";

my ($drive, $directory, $filename, $ext1, $lastdir, $filebody) = Deps::SplitFilePath($ProgramPath);
my $BaseDir      = "$drive$directory";
$Vars{StartTime} = &BuildDateString(time());
$Vars{IPAddress} = $ENV{REMOTE_ADDR};

if(!$UseThreadForChild and $ARGV[0] eq 'action') {
	&ExecuteExternalCommand(*STDOUT, @ARGV);
	exit;
}
else {
	print "Program Path: $ProgramPath$LF" if($Vars{LocalPrintLevel} >= 1);
	print "Base Dir    : $BaseDir$LF"     if($Vars{LocalPrintLevel} >= 1);
	print "Ini File    : $IniFilePath$LF" if($Vars{LocalPrintLevel} >= 1);
}

&Initialize();

$BatchThread   = threads->new(\&BatchLoop, "1");
$CommandThread = threads->new(\&ConnectionLoop);
$BatchThread->join();
$CommandThread->join();

close(SOCK);

exit;

#==========================================
# スレッド関係サブルーチン
#==========================================
sub ExecuteExternalCommand
{
	my ($io, $action, @arg) = @_;
	if($action eq 'action') {
		my $a;
		($io, $a, $action, @arg) = @_;
	}

#print $io "In ActionThread: action: $action$LF";
	my $localcmd = Deps::MakePath($BaseDir, ["remotecmd", $action], 0);
#print "localcmd: $localcmd<br>\n";
	if(-f $localcmd) {
		my @EnvBackup = @ENV;
		foreach my $key (keys %Vars) {
			$ENV{$key} = $Vars{$key};
		}
		$localcmd = "$localcmd " . join(' ', @arg);
		print $io "CMD: $action [$localcmd]\n";
		my $result = `$localcmd`;
		print $io $result;
		@ENV = @EnvBackup;
	}
	else {
		print $io "Invalid command: [$localcmd] => [$action]\n";
	}
}

sub BatchLoop
{
	my ($name) = @_;
print "Enter LoopThread$LF" if($Vars{LocalPrintLevel} >= 2);

	&ReadIniFile();

	while(1) {
		if($Vars{status} eq 'stop') {
			print "ActionThread [$name][$Vars{status}]: Exit loop$LF" if($Vars{LocalPrintLevel} >= 2);
			&WaitVars();
			$Vars{RealStatus} = 'terminated';
			cond_broadcast(%Vars);
			last;
		}

		if($Vars{status} eq 'wait') {
			print "ActionThread [$name][$Vars{status}]: Waiting... ", time(), "$LF" if($Vars{LocalPrintLevel} >= 2);
			&WaitVars();
			$Vars{RealStatus} = 'waiting';
			cond_broadcast(%Vars);
		}
		else {
			&WaitVars();
			$Vars{RealStatus} = 'running';
			cond_broadcast(%Vars);
			print "ActionThread [$name][$Vars{status}]: Doing loop: ", time(), "$LF" if($Vars{LocalPrintLevel} >= 2);
			if(chdir($Vars{ExecuteBaseDir})) {
#				system("ls");
			}
			else {
				print("chdir [$Vars{ExecuteBaseDir}] failed.\n") if($Vars{LocalPrintLevel} >= 1);
				&MySleep($Vars{BatchLoopInterval});
				next;
			}

			for(my $i = 1 ; ; $i++) {
				my $key = "QueueDir$i";
				my $QueueDir = $Vars{$key};
#print "$i:$key: $QueueDir\n";
				last if(!defined $QueueDir);
				next if($QueueDir eq '' or $QueueDir eq 'none');

				if(!-d $QueueDir) {
					print "$QueueDir: Error: [$QueueDir] does not exist.\n" if($Vars{LocalPrintLevel} >= 1);
					next;
				}

				my $ret = &ExecuteBatch($QueueDir);
				if($ret) {
					next;
				}
				else {
#					last;
				}
			}
		}

		threads->yield();
		&MySleep($Vars{BatchLoopInterval});
	}
}

sub ExecuteBatch
{
	my ($QueueDir) = @_;

	my $time = Utils::BuildDateString(time());

	if(chdir($Vars{ExecuteBaseDir})) {
	}
	else {
		print "$QueueDir: Error: Failed to change directory to [$QueueDir].\n";
		return 1;
	}

	my @files;
	if($Vars{BatchMode} =~ /CIF/i) {
		@files = &FindFiles(Deps::MakePath($QueueDir, "*.cif", 0), $Vars{SortBy});
	}
	if($Vars{BatchMode} =~ /Dir/i) {
		my $fmask = Deps::MakePath($QueueDir, "*", 0);
		@files = &FindFiles($fmask, $Vars{SortBy});
	}

	if(@files == 0) {
		print("$QueueDir: No active target found ($time).\n") if($Vars{LocalPrintLevel} >= 1);
		&UpdateLog($Vars{LogFile}, "No active target found ($time).") if($Vars{WriteNoActiveTargetLog});
		&WaitVars();
		$Vars{WriteNoActiveTargetLog} = 0;
		cond_broadcast(%Vars);
		return 1;
	}
	&WaitVars();
	$Vars{WriteNoActiveTargetLog} = 1;
	cond_broadcast(%Vars);

	my ($SourcePath, $ExecuteDirName);
	for(my $i = 0 ; $i < @files ; $i++) {
		my $path = $files[$i];
		$time = Utils::BuildDateString(time());
		my $FileTime = Utils::GetWriteDate($path);

		if($path =~ /$Vars{SkipFiles}/) {
			print("$QueueDir: Moved directory/file [$files[$i]] found: Skip ($time).\n") if($Vars{LocalPrintLevel} >= 1);
			next;
		}

		my $SourceScriptFilePath     = &FindFirstMatch(Deps::MakePath($path, $Vars{ScriptFileMask}), "name");
		my $SourceScriptTemplatePath = &FindFirstMatch(Deps::MakePath($QueueDir,   $Vars{ScriptTemplateName}), "name");

		if($Vars{BatchMode} =~ /CIF/i) {
			if(!-f $path) {
#				print("$QueueDir [$Vars{BatchMode} mode]: [$path] is not a valid CIF file. ($time).\n") 
#						if($Vars{LocalPrintLevel} >= 1);
				next;
			}
			if(!-f $SourceScriptTemplatePath) {
				print("$QueueDir [$Vars{BatchMode} mode]: Template [$SourceScriptTemplatePath] is not found. ($time).\n") 
							if($Vars{LocalPrintLevel} >= 1);
				next;
			}
		}
		if($Vars{BatchMode} =~ /Dir/i) {
			if(!-d $path) {
				next;
			}
			next if(!-f $SourceScriptFilePath);
		}

		my ($drive, $directory, $filename, $ext, $lastdir, $filebody) = Deps::SplitFilePath($path);
		$ExecuteDirName = $filebody;

		if(-e $ExecuteDirName) {
			print("$QueueDir: Directory [$ExecuteDirName] exists: Skip ($time).\n") if($Vars{LocalPrintLevel} >= 1);
			next;
		}

		print("$QueueDir: $i: $files[$i] ($time)\n") if($Vars{LocalPrintLevel} >= 2);
		$SourcePath = $path;
		last;
	}

	my $SourceScriptFilePath     = &FindFirstMatch(Deps::MakePath($SourcePath, $Vars{ScriptFileMask}),     "name");
	my $SourceScriptTemplatePath = &FindFirstMatch(Deps::MakePath($QueueDir,   $Vars{ScriptTemplateName}), "name");

	if(!defined $SourcePath) {
		print("$QueueDir: No active target found ($time).\n") if($Vars{LocalPrintLevel} >= 1);
		&UpdateLog($Vars{LogFile}, "$QueueDir: No active target found ($time).");
		&WaitVars();
		$Vars{WriteNoActiveTargetLog2} = 0;
		cond_broadcast(%Vars);
		return 0;
	}
	$Vars{WriteNoActiveTargetLog2} = 1;

# ファイルを QueueDir から WorkDir へコピー
	my $cmd;
	if($Vars{BatchMode} =~ /CIF/i) {
		my $TargetScriptPath = Deps::MakePath($ExecuteDirName, $Vars{ScriptFileName});
#print "Target: [$TargetScriptPath]\n";
		$cmd = "mkdir $ExecuteDirName; cp $SourcePath $ExecuteDirName; cp $SourceScriptTemplatePath $TargetScriptPath";
	}
	if($Vars{BatchMode} =~ /Dir/i) {
		$cmd = "cp -r $SourcePath $ExecuteDirName";
	}
print "Copy: [$cmd]\n";

	my $ret;
	if($ret = system($cmd)) {
		my $error = "$QueueDir: Execute [$cmd] failed [$ret]";
		print("  Error: $error\n") if($Vars{LocalPrintLevel} >= 1);
		&WaitVars();
		$Vars{LastError} = $error;
		cond_broadcast(%Vars);
		return 0;
	}
	else {
		my $mess = "$QueueDir: Executed: [$cmd]";
		print("$mess\n") if($Vars{LocalPrintLevel} >= 1);
		&WaitVars();
		$Vars{LastMessage} = $mess;
		cond_broadcast(%Vars);
	}

# ディレクトリィ変更
print "chdir: [$ExecuteDirName]\n";
	if(chdir($ExecuteDirName)) {
#		system("ls");
	}
	else {
		my $error = "$QueueDir: chdir [$ExecuteDirName] failed";
		print("  Error: $error\n") if($Vars{LocalPrintLevel} >= 1);
		&WaitVars();
		$Vars{LastError} = $error;
		cond_broadcast(%Vars);
		return 0;
	}

# Source名変更
	$cmd = "mv $SourcePath $SourcePath.moved";
print "mv: [$cmd]\n";
	if($ret = system($cmd)) {
		my $error = "$QueueDir: Execute [$cmd] failed";
		print("  Error: $error\n") if($Vars{LocalPrintLevel} >= 1);
		&WaitVars();
		$Vars{LastError} = $error;
		cond_broadcast(%Vars);
		return 0;
	}

	my $ScriptFilePath = &FindFirstMatch(Deps::MakePath("./", $Vars{ScriptFileMask}), "name");
	my $mess = "$QueueDir: Execute [$ScriptFilePath] in [$ExecuteDirName]";
	print("\n\n$mess\n") if($Vars{LocalPrintLevel} >= 1);

	&WaitVars();
	$Vars{ExecuteScriptFilePath}    = $ScriptFilePath;
	$Vars{SourceScriptFilePath}     = $SourceScriptFilePath;
	$Vars{SourcePath}               = $SourcePath;
	$Vars{SourceScriptTemplatePath} = $SourceScriptTemplatePath;
	$Vars{ExecutingDir}             = $ExecuteDirName;
	$Vars{CurrentCommand}           = $ScriptFilePath;
	$Vars{LastMessage}              = $mess;
	cond_broadcast(%Vars);
	&UpdateLog($Vars{LogFile}, "Execute [$ScriptFilePath] in [$ExecuteDirName]: Start");

	if($ret = system($ScriptFilePath)) {
		my $error = "$QueueDir: Execute [$ScriptFilePath] failed [$ret] in [$ExecuteDirName]";
		print("  Error: $error\n") if($Vars{LocalPrintLevel} >= 1);
		&WaitVars();
		$Vars{LastError} = $error;
		cond_broadcast(%Vars);
		&UpdateLog($Vars{LogFile}, "Error: Execute [$ScriptFilePath] failed [$ret] in [$ExecuteDirName].");
		return 0;
	}

	&UpdateLog($Vars{LogFile}, "Execute [$ScriptFilePath] in [$ExecuteDirName]: Finish");

	return 1;
}

sub TimeOut
{
	print "time_out\n";
	threads->yield();
}

sub CommandLoop {
	my ($port, $iaddr, $name, $CLIENT) = @_;
#print "Enter to action$LF";

	my $fh = select($CLIENT);
	$|=1;
	select($fh);

	my $now = localtime(time);
	print $CLIENT "Local time: $now$LF";

#	alarm(1);
#	$SIG{ALRM} = 'TimeOut';

	while(1) {
		my $ClientCommand = <$CLIENT>;

		$ClientCommand = &ConvertString($ClientCommand);
print $CLIENT "ClientCommand: $ClientCommand$LF";
		my $lcmd = lc $ClientCommand;
#print $CLIENT "ClientCommand: $lcmd$LF";

		my $IPAddress = inet_ntoa($iaddr);
		if($IsLimitAccess and !&IsAllowedIPAddress($IPAddress, @AllowedIPAddresses)) {
			print $CLIENT "Your access is not allowed ($IPAddress).$LF";
			return 0;
		}

		if($ClientCommand eq '' or $ClientCommand =~ /^(bye|exit|quit)$/i) {
			print $CLIENT "Bye!$LF";
#			close($CLIENT);
			return 0;
		}
		elsif($ClientCommand eq '-reboot') {
			if($AllowReboot) {
				print $CLIENT "Launch [$Perl $ProgramPath] and shutdown this server.$LF";
				close($CLIENT);
				close(SOCK);
				exec("$Perl $ProgramPath");
				return 0;
			}
			else {
				print $CLIENT "Error: [-reboot] is not allowed.$LF";
			}
		}
		elsif($ClientCommand eq '-kill') {
			if($AllowTerminate) {
				print $CLIENT "Kill this server.$LF";
				kill('KILL', 0);
				return -1;
			}
			else {
				print $CLIENT "Error: [-kill] is not allowed.$LF";
			}
		}
		elsif($ClientCommand eq '-terminate') {
			if($AllowTerminate) {
				print $CLIENT "Shutdown this server.$LF";
				&WaitVars();
				$Vars{status} = 'stop';
				cond_broadcast(%Vars);
#				$BatchThread->detach();
#				$CommandThread->detach();
				close($CLIENT);
				close(SOCK);
				return -1;
			}
			else {
				print $CLIENT "Error: [-terminate] is not allowed.$LF";
			}
		}
		elsif($lcmd =~ /^(help|\?)$/i) {
			print $CLIENT "Commands:$LF";
			print $CLIENT "  bye | exit | quit | 'blank': Disconnect connection$LF";
			my $Allowed = ($AllowReboot)? 'allowed' : 'disabled';
			print $CLIENT "  -reboot              : Reboot server ($Allowed)$LF";
			print $CLIENT "  help | ?             : Show this help$LF";
			print $CLIENT "  ListQueue | lq       : List queue jobs$LF";
			print $CLIENT "  List                 : List variables$LF";
			print $CLIENT "  Recover [job_name]   : Remove '.moved' from job_name in queue dirs$LF";
			print $CLIENT "  PrintEnv             : List environment variables$LF";
			print $CLIENT "  print [text]: Print Text$LF";
			print $CLIENT "  pwd|cwd              : Print working dir$LF";
			print $CLIENT "  cd|chdir             : Change working dir$LF";
			print $CLIENT "  status=[run|wait|sto]: Control batch looop$LF";
			print $CLIENT "  key=val              : Set 'val' to the variable 'key'$LF";
			print $CLIENT "  Others: Exec [$Perl $ProgramPath action 'command'$LF";
		}
		elsif($lcmd eq 'pwd' or $lcmd eq 'cwd') {
			my $cwd = cwd();
			print $CLIENT "Current directory: [$cwd]$LF";
		}
		elsif($ClientCommand =~ /^(cd|chdir)\s+(.*)$/i) {
			my $dir = $1;
			if(chdir($dir)) {
				print $CLIENT "Working directory changed to [$dir]$LF";
			}
			else {
				print $CLIENT "Error: Failed to change working directory to [$dir]$LF";
			}
			my $cwd = cwd();
			print $CLIENT "Current directory: [$cwd]$LF";
		}
		elsif($ClientCommand =~ /^(lq|listq)\w*\s*(.*)$/i) {
			&ListQueue($CLIENT, $2);
		}
		elsif($ClientCommand =~ /^(fq|findq)\w*\s*(.*)$/i) {
			my $queue = $2;
			my ($dir, $fname, $path) = &FindQueue($CLIENT, $queue);
			if(-e $path) {
				print $CLIENT "Next queue for [$queue]: [$path]\n";
			}
			else {
				print $CLIENT "Error: Next queue for [$queue] is not found\n";
			}
		}
		elsif($ClientCommand =~ /^recover\s+(.*)$/i) {
			&RecoverJob($CLIENT, $1);
		}
		elsif($lcmd eq 'list') {
			&ListVars($CLIENT);
		}
		elsif($lcmd eq 'printenv') {
			&PrintEnv($CLIENT);
		}
		elsif($ClientCommand =~ /^print\s+(.*)$/i) {
			print $CLIENT "$1$LF";
		}
		elsif($lcmd eq 'init') {
			print $CLIENT "Read [$IniFilePath]$LF";
			&ReadIniFile();
		}
		elsif($ClientCommand =~ /^(\w+)\s*=\s*(\S.*)\s*$/) {
			my $key = $1;
			my $val = $2;
			&WaitVars();
			$Vars{$key} = $val;
			cond_broadcast(%Vars);
			print $CLIENT "set [$val] to variable=[$key]$LF";
			if($key eq 'terminal') {	&SetLF($val);
			}
		}
		else {
			if($UseThreadForChild) {
				my $Thread = threads->new(\&ExecuteExternalCommand, $CLIENT, $ClientCommand);
				$Thread->join();
			}
			else {
				my $ret = `$Perl $ProgramPath action $ClientCommand`;
				print $CLIENT "$ret$LF";
			}
		}

		&UpdateVars();
		last if($Vars{OneLineResponse});

		&MySleep($Vars{CommandLoopInterval});
	}

    return 1;
}

sub ConnectionLoop {
	my $CLIENT;
	my $paddr;
	my ($port, $iaddr);
	my $name;
	while(1) {
		$paddr = accept($CLIENT, SOCK);
		if(!$paddr) {
			close($CLIENT);
			return 0;
		}
#print "paddr: $paddr$LF";
		($port, $iaddr) = sockaddr_in($paddr);
		$name = gethostbyaddr($iaddr, AF_INET);
#print "paddr: $port, $iaddr, $name$LF";

		my $ChildThread = threads->new(\&CommandLoop, $port, $iaddr, $name, $CLIENT);
		$ChildThread->join();
#		my $ret = &CommandLoop($port, $iaddr, $name, $CLIENT);

		close($CLIENT);
		undef $CLIENT;

#		last if($ret == -1);

		threads->yield();

		&UpdateVars();
		&MySleep($Vars{CommandLoopInterval});
#print "Exit from action$LF";
	}

	return 1;
}

#===============================================
#===============================================
# 非スレッドサブルーチン
#===============================================
#===============================================

sub signal_handler {
#	eval{ close(XXXXX);  };  # グローバルで開いているファイルがあれば、閉じる
	my ($sig) = @_;

	if($sig eq 'INT') {
		print "Ctrl-C is pressed.$LF";
	}
	else {
		print "[$sig] is accepted.$LF";
	}
	setpgrp() if($^O ne 'MSWin32');	# I *am* the leader 
	$SIG{$sig} = 'IGNORE';
	kill($sig, 0);					# death to all-comers
	die "killed by $sig"; 

	exit(0);
}

sub SetLF
{
	my ($terminal) = @_;
	if($terminal eq 'windows') {
		$LF = "\r\n";
	}
	elsif($terminal eq 'mac') {
		$LF = "\n\r";
	}
	else {
		$LF = "\n";
	}
}

sub Initialize {
	&SetLF($Vars{terminal});

	if($TrapSignals) {
		$SIG{INT}  = \&signal_handler;         # Ctrl-C が押された場合
		$SIG{HUP}  = \&signal_handler;         # HUP  シグナルが送られた場合
#		$SIG{QUIT} = \&signal_handler;         # QUIT シグナルが送られた場合
#		$SIG{KILL} = \&signal_handler;         # KILL シグナルが送られた場合
#		$SIG{TERM} = \&signal_handler;         # TERM シグナルが送られた場合
	}

#===============================================
# Daemon fork off process
#===============================================
	if($DaemonMode) {
		my $ChildPId = fork;
		if(!defined $ChildPId) {
			die "Can not fork" 
		}
		elsif($ChildPId == 0) {
#print "This is child process.$LF";
		}
		else {
#print "This is parent process. Exit$LF";
		exit(0); # 親が終了する
		}
	}

	my $proto = getprotobyname('tcp');
	socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
	setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die "setsockopt: $!";

	bind(SOCK, sockaddr_in($ServerPort, INADDR_ANY)) or die "bind: $!";
	listen(SOCK, SOMAXCONN) or die "listen: $!";
print "Access by http://localhost:$ServerPort/$LF" if($Vars{LocalPrintLevel} >= 0);

#===============================================
# Daemonプロセスへの移行に伴い、入出力をconsoleから切り離す
#===============================================
	if($DaemonMode) {
		if($RedirectIO) {
			open(STDIN,  "</dev/null");
			open(STDOUT, ">/dev/null");
			open(STDERR, ">&STDOUT");
		}

		if($^O eq 'MSWin32') {
		}
		else {
			setsid(); #セッションリーダーになる
#			chdir('/');
			umask(0); #ファイルモードの作成マスクをリセットする
			$ENV{PATH} = "/bin:sbin:/usr/bin:/usr/sbin";
		}
	}
}

sub UpdateLog
{
	my ($LogFile, $message) = @_;
	return if($LogFile eq '');

	my $DateString = Utils::BuildDateString(time());

	my $in = new JFile;
	my $content = $in->ReadFile($Vars{LogFile}, undef, undef);
	my $out = new JFile;
	if(!$out->Open($Vars{LogFile}, "w")) {
		print "Error: Can not write to log file [$Vars{LogFile}].\n" if($Vars{LocalPrintLevel} >= 0);
		return;
	}
	$out->print("$DateString: $message\n");
	$out->print($content);
	$out->Close();
}

sub PrintEnv
{
	my ($CLIENT) = @_;

	print $CLIENT "List of environment variabls:$LF";
	foreach my $key (sort keys %ENV) {
		print $CLIENT "  $key=$ENV{$key}$LF";
	}
}

sub ListVars
{
	my ($CLIENT) = @_;

	&UpdateVars();

	print $CLIENT "List of variabls:$LF";
	foreach my $key (sort keys %Vars) {
		print $CLIENT "  $key=$Vars{$key}$LF";
	}
}

sub ConvertString
{
	my ($s) = @_;

	&UpdateVars();
	foreach my $key (sort keys %Vars) {
		$s =~ s/\$$key([^\w_]?)/$Vars{$key}$1/g;
	}
	foreach my $key (sort keys %ENV) {
		$s =~ s/\$$key([^\w_]?)/$ENV{$key}$1/g;
	}
	Utils::DelSpace($s);

	return $s;
}

sub ReadIniFile
{
	&WaitVars();
	my $ini = new IniFile;
	$ini->ReadAll($IniFilePath);
	foreach my $key (keys %$ini) {
		next if($key =~ /^System\//);
		$Vars{$key} = &ConvertString($ini->{$key});
	}
	cond_broadcast(%Vars);
}

sub UpdateVars
{
	&WaitVars();
	cond_broadcast(%Vars);
}

sub WaitVars
{
	lock(%Vars);
	until(%Vars) {
		threads->yield();
		sleep(1);
		print "wait for get$LF" if($Vars{LocalPrintLevel} >= 2);
		cond_wait(%Vars);
	}
}

sub MySleep
{
	my ($sec) = @_;
	return 1 if($sec == 0);

	my $start = time();
	while(1) {
		threads->yield();
		sleep(1);
		last if(time() - $start >= $sec);
	}
	return 1;
}

sub IsAllowedIPAddress
{
	my ($IPAddress, @allowed) = @_;

	foreach my $exp (@allowed) {
		return 1 if($IPAddress =~ /$exp/i);
	}
	return 0;
}

sub BuildDateString
{
	my ($time) = (@_);
	
	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($time);
	$year += 1900;
	$mon++;
	my $str = sprintf("%02d/%02d/%02d %02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec);
	return $str;
}

sub FindFiles
{
	my ($fmask, $sortby) = @_;

	my @f = glob($fmask);
	if($sortby eq 'name') {
		@f = sort { $a cmp $b; } @f;
	}
	elsif($sortby eq 'name-r') {
		@f = sort { $b cmp $a; } @f;
	}
	elsif($sortby eq 'time') {
		@f = sort { Utils::GetWriteDate($a) <=> Utils::GetWriteDate($b); } @f;
	}
	elsif($sortby eq 'time-r') {
		@f = sort { Utils::GetWriteDate($b) <=> Utils::GetWriteDate($a); } @f;
	}
	else {
	}

	return @f;
}

sub FindFirstMatch
{
	my ($fmask, $sortby) = @_;

	my @f = &FindFiles($fmask, $sortby);
	return $f[0];
}

sub FindQueue
{
	my ($CLIENT, $fname, $mode) = @_;

	Utils::DelSpace($fname);
	for(my $i = 1 ; ; $i++) {
		my $key = "QueueDir$i";
		my $QueueDir = $Vars{$key};
		last if(!defined $QueueDir);
		next if($QueueDir eq '' or $QueueDir eq 'none');

		my $path = Deps::MakePath($QueueDir, $fname);
		$path = "$path.moved" if($mode eq 'moved');

		if(-e $path) {
			return ($QueueDir, $fname, $path);
		}
	}
	return ();
}

sub RecoverJob
{
	my ($CLIENT, $arg) = @_;

	my ($dir, $fname, $path) = &FindQueue($CLIENT, $arg, "moved");
	if(-e $path) {
		print $CLIENT "[$path] found.\n";
		my $newpath = Deps::MakePath($dir, $arg);

		my $cmd = "mv $path $newpath";
		system($cmd);
		if(-e $newpath) {
			print $CLIENT "  [$cmd] completed\n";
		}
		else {
			print $CLIENT "  Execute [$cmd] failed.\n";
		}
	}
	else {
		print $CLIENT "Error: [$arg.moved] not found.\n";
		return;
	}

	my ($drive, $directory, $filename, $ext, $lastdir, $filebody) = Deps::SplitFilePath($path);
	my $ExecuteDirName = Deps::MakePath($Vars{ExecuteBaseDir}, $filebody);
	if(-e $ExecuteDirName) {
		my $newpath;
		for(my $i = 1 ; ; $i++) {
			$newpath = "$ExecuteDirName.$i";
			last if(!-e $newpath);
		}
		if(!defined $newpath) {
			print $CLIENT "Error: Existing dir [$ExecuteDirName] could not be moved.\n";
			return;
		}

		my $cmd = "mv $ExecuteDirName $newpath";
		system($cmd);
		if(-e $newpath) {
			print $CLIENT "  [$cmd] completed\n";
		}
		else {
			print $CLIENT "  Execute [$cmd] failed.\n";
		}
	}

}

sub ListQueue
{
	my ($CLIENT, $arg) = @_;

	my $c = 1;
	for(my $i = 1 ; ; $i++) {
		my $key = "QueueDir$i";
		my $QueueDir = $Vars{$key};
		last if(!defined $QueueDir);
		next if($QueueDir eq '' or $QueueDir eq 'none');

		if(!-e $QueueDir) {
			print $CLIENT "$QueueDir: Not exist\n";
			next;
		}
		if(!-d $QueueDir) {
			print $CLIENT "$QueueDir: Not directory\n";
			next;
		}
		print $CLIENT "$QueueDir:\n";

		my @files;
		if($Vars{BatchMode} =~ /CIF/i) {
			@files = &FindFiles(Deps::MakePath($QueueDir, "*.cif", 0), $Vars{SortBy});
		}
		if($Vars{BatchMode} =~ /Dir/i) {
			my $fmask = Deps::MakePath($QueueDir, "*", 0);
			@files = &FindFiles($fmask, $Vars{SortBy});
		}

		for(my $j = 0 ; $j < @files ; $j++) {
			my $path = $files[$j];
			my $FileTime = Utils::GetWriteDate($path);

#print $CLIENT "  exaine path: [$path] by [$Vars{SkipFiles}]\n";
			if($path =~ /$Vars{SkipFiles}/) {
				print $CLIENT "  [m] $path: Moved directory/file\n";
				next;
			}

			my $SourceScriptFilePath     = &FindFirstMatch(Deps::MakePath($path, $Vars{ScriptFileMask}), "name");
			my $SourceScriptTemplatePath = &FindFirstMatch(Deps::MakePath($QueueDir,  $Vars{ScriptTemplateName}), "name");

			if($Vars{BatchMode} =~ /CIF/i) {
				if(!-f $path) {
					print $CLIENT "  [e] $path: Not a valid CIF file\n";
					next;
				}
				if(!-f $SourceScriptTemplatePath) {
					print $CLIENT "  [e] $path: Template [$Vars{ScriptTemplateName}] not found\n";
					next;
				}
			}
			if($Vars{BatchMode} =~ /Dir/i) {
				if(!-d $path) {
					print $CLIENT "  [e] $path: Not a directory\n";
					next;
				}
				if(!-f $SourceScriptFilePath) {
					print $CLIENT "  [e] $path: Script file [$Vars{ScriptFileMask}] not foun\n";
				}
			}

			my ($drive, $directory, $filename, $ext, $lastdir, $filebody) = Deps::SplitFilePath($path);
			my $ExecuteDirName = $filebody;

			if(-d $ExecuteDirName) {
				print $CLIENT "  [e] $path: Execute directory [$ExecuteDirName] not found\n";
				next;
			}

			print $CLIENT "  [$c] $path: Executable in [$ExecuteDirName]\n";
			$c++;
		}

	}
}

