#!/usr/bin/perl -I/server/phexproxy/PhexProxy

use strict;
# use warnings;

#########################################
##### Author:		Sebastian Enger / B.Sc
##### CopyRight:	BitJoe GmbH
##### LastModified	26.1.2008
##### Function:		Hauptdatei für PhexProxy
##### Todo:			for statt foreach: ist schneller
##### Hint:			minimum StringLength of one Result is about 70 Chars long
########################################

# root: -20 <> 20 | normal: 0 <> 20
my $ServicePriority = "-5";				

system("renice $ServicePriority $$");
system("clear");

### TODO: Encoding unicode  / zeile 499

use Socket;
use DB_File;
use IO::Select;
use Fcntl ':flock';
# use POSIX ":sys_wait_h";
# use POSIX qw(ceil);
use Data::Dumper;
use PhexProxy::IO;
use PhexProxy::SMS;
use PhexProxy::Mail;
use PhexProxy::Gzip;
use PhexProxy::Time;
use PhexProxy::Phex;
use PhexProxy::Daemon;
use PhexProxy::Logging;
use PhexProxy::CheckSum;
use PhexProxy::PhexSortRank;
use PhexProxy::CryptoLibrary;
use PhexProxy::LicenceManagement;
# use Math::BigFloat;			# perl -MCPAN -e 'force install "Math::BigFloat"'


# definiere die paris host server hier, die angefragt werden sollen
my %ParisHosts				= (
	"87.106.63.182"			=> "bitjoe.at [Quad Core 2.5Ghz]",				# bitjoe.at
	"81.169.141.129"		=> "phexproxy [celeron-2.4Ghz]",				# phexproxy celeron
	"85.214.39.76"			=> "alpha64.info [Dual Core 2.3Ghz]",			# bitjoe.de / alpha64.info
#	"85.214.77.110"			=> "alleonleinshops.com [celeron-2.4Ghz]",		# alleonleinshops.com
#	"85.214.122.130"		=> "usenext mirror zoozle [Dual Core 2.3Ghz]",	# usenext mirror zoozle
);
my $FastestDistributedServer = "87.106.63.182";	# Ergebnisse vom schnellsten Server sollen zuerst verarbeitet werden


my $HandyClientHostname;
my $HandyClientSocket;

my %ParisSocketsHash		= ();
my $ParisSocketsHash		= ();
my %ParisResultCache		= ();
my $ParisResultCache		= ();
my $ParisSocketsHashRef		= ();

my $DONE					= 0;
my $port					= 7773;		# Port von Programm $0
my $MaxNoResultTries		= 4;		# nach maximal 4 erfolglosen versuchen wird versucht einen eintrag aus dem Cache zu lesen
my $MaxCacheValidTime		= 89723;	# in sekunden
my $MaximumAllowedLoad		= 40;		# for development tests, sonst auf 10 stellen
my $MaxResultServeTimeout	= 7;		# wait max of time for results for while read ParisResult socket!
my $MaxRetryOnFailedIORead	= 2;		# maximal sooft nochmal versuchen den Server neu anzufragen nach ergebnissen
my $MaxRetryOnFailedIOSleep	= 1;		# zwischen den abholvorgaengen von einem server zufällig bis zu X sec schlafen
my $NUMBEROFRESULTS			= 15;		# anzahl der ergebnisse,die dem client gesendet werden sollen
my $ParisHostCount			= keys( %ParisHosts );

# Vorher: 
# my $DBFlatFile				= "/server/phexproxy/cache/DistributedConnections.db"; unlink $DBFlatFile || system("/bin/rm -rf $DBFlatFile");

# Später - wenn pro Core $0 lauft:  
my $DBFlatFile				= "/server/phexproxy/cache/DistributedConnections_$$.db"; unlink $DBFlatFile || system("/bin/rm -rf $DBFlatFile");
my $ResultCacheFile			= "/server/phexproxy/cache/DistributedResultCache.db";
my $TmpFilePath				= "/server/phexproxy/tmp";					# für NoResultQueryID speichern
my $FILE					= '/server/phexproxy/IDS'; mkdir $FILE;
my ($ServerIPAdress)		= `LANG=US_us ifconfig eth0`=~/inet addr:([\d.]+)/;
my $MaxRawDataToProcess		= 3000 * 1024;								# 40 KB an Rohdaten sollen maximal verarbeitet werden, der Rest wird abgeschnitten
$MaxRawDataToProcess		= sprintf("%.0f", $MaxRawDataToProcess);	# Immer aufrunden
																		# Hinweis $MaxRawDataToProcess wird momentan nicht beachtet - es werden keine Ergebnisse abgeschnitten


use constant VERSION		=> "DistributedPhexProxy 0.9.n -SMS Benachrichtigung- 13.2.2008 @ 13.30 Uhr";
use constant PIDFILE		=> "/server/phexproxy/phexdistributedproxy-pid.txt";

use constant CRLF			=> "\r\n";
use constant SEPARATOR		=> "#";		# former '##'
use constant LINEEND		=> "\r";



# Zombie Catcher
#sub REAPER {
#
#	my $stiff;
#	while (($stiff = waitpid(-1, &WNOHANG)) > 0) {
#		warn ("child $stiff terminated -- status $?");
#		$DONE--;
#		# delete $children{$stiff};
#	}; # while (($stiff = waitpid(-1, &WNOHANG)) > 0) {
#
#	$SIG{CHLD} = \&REAPER;
#}
#$SIG{CHLD} = \&REAPER;


my $IO						= PhexProxy::IO->new();
my $Socket					= PhexProxy::IO->CreateHandyDistributedSocket( $ServerIPAdress, $port );
my $SMS						= PhexProxy::SMS->new();
my $GZIP					= PhexProxy::Gzip->new();
my $MAIL					= PhexProxy::Mail->new();
my $TIME					= PhexProxy::Time->new();
my $PHEX					= PhexProxy::Phex->new();
my $Token					= $PHEX->readToken();
my $IN						= IO::Select->new($Socket);
my $LOGGING					= PhexProxy::Logging->new();
my $CHECK					= PhexProxy::CheckSum->new();
my $SORTRANK				= PhexProxy::PhexSortRank->new();
my $CRYPTO					= PhexProxy::CryptoLibrary->new();
my $LICENCE					= PhexProxy::LicenceManagement->new();

unlink PIDFILE;
#&init_server(PIDFILE);

die "can't setup $0 socket\n" unless $Socket;
die "No PhexProxys defined\n" if ( $ParisHostCount <= 0 );
print "[Server $0 Version " . VERSION . " accepting clients on host $ServerIPAdress port $port with Service Priority '$ServicePriority']\n";

# signal handler installieren
$SIG{'TERM'} = $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = sub { close($Socket); unlink $DBFlatFile || system("/bin/rm -rf $DBFlatFile"); die "Signal Handler: $0 has been killed \n";  };


########################
##### while accept loop
########################

while (!$DONE) {

	tie(%ParisSocketsHash, 'DB_File', $DBFlatFile, O_RDWR|O_CREAT ) || die "can't open $DBFlatFile: $!";

	next unless $IN->can_read;
	next unless $HandyClientSocket	= $Socket->accept();

	my $HandyClientHostname;

	eval { 
		# from Socket.pm;
		my $client_ip			= getpeername($HandyClientSocket);
		my ($port, $ipaddr)		= unpack_sockaddr_in($client_ip);
		$HandyClientHostname	= inet_ntoa($ipaddr);	# ist IP Adresse
	}; # eval { 
	

	if($@){

		# wird hoffentlich nie erreicht, denn sonst kann nicht bestimmt werden ,wie oft schon erfolglos gesucht wurde
		$HandyClientHostname = "127.0.0.1";
		print "Fehler $@ aufgetreten\n";

	}; # if($@){


	my $MD5		= $CHECK->MD5ToHEX($HandyClientHostname . time() . $CRYPTO->URandom("6024") . $$ );	# MD5 Wert für gesicherte Zuordnung Verbindung<->$DBFlatFile Eintrag
	
	# select (undef, undef, undef, rand($MaxRetryOnFailedIOSleep) );	
	#select (undef, undef, undef, 1.0 );	
	
	my $child	= launch_child();
	

	unless ($child) {

		close $Socket;

		# Automatischer Load Check
		my $LoadScalarRef					= $IO->ReadFileIntoScalar("/proc/loadavg");
		my ($CurrentLoad,undef,undef,undef) = split(" ", ${$LoadScalarRef});
		if ( $CurrentLoad > $MaximumAllowedLoad ) {
			
			print "Current Load is '$CurrentLoad' and higher than allowed\n";
			$IO->writeSocket( $HandyClientSocket, $GZIP->gzip_compress_string("FC 909") . CRLF ); # FC 909: load auf dem server zu hoch 
			close $HandyClientSocket;
			exit(0);

		}; # if ( $CurrentLoad > $MaximumAllowedLoadForPhex ) {

	
		# Nehme den Content vom Handy an
		my $ReadFromClient		= $IO->readSocket( $HandyClientSocket );
		my $ReadFromClientOrg	= $ReadFromClient;
		my @ReadFromClient		= split('##', $ReadFromClient );
	

		# Abschnitt Entschlüsselung Handynachricht
		####################### crypto api implementatione #####################
		#	$IOReadFromClient[0] = MD5 Wert der Verschlüsselten Nachricht
		#	$IOReadFromClient[1] = der public schlüssel 
		#	$IOReadFromClient[2] = die verschlüsselte nachricht
			
		my $PrivateCryptoKey = $CRYPTO->GetPrivateCryptoKeyFromDatabase( $ReadFromClient[1] );
		if ( $PrivateCryptoKey == -1 ) {
		
			print STDERR "Error: Crypto false privatekey - got: '$ReadFromClient[1]'\n";
			print Dumper @ReadFromClient;
			$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string("FC 703") . CRLF);	# Crypto false privatekey
			close $HandyClientSocket;
			exit(0);

		}; # if ( $PrivateKey == -1 ) {}

		
		# Eigentliche Entschlüsselung mittels Java AES
		my $PlainText			= $CRYPTO->Decrypt( $PrivateCryptoKey, $ReadFromClient[2] );
		my @IOReadFromClient	= ();	# resette das wichtige Array
	
		my ( $stuff	)			= split("\r\n", $PlainText ); 
		@IOReadFromClient 		= split("##", $stuff ); 


		# Abschnitt Licence Check
		my ( $Status, $FileTypeHashRef ) = $LICENCE->CheckLicence( \@IOReadFromClient );

		if ( $Status != 1 ) {
			
		#	print STDERR "CheckLicence failed - from: IP $HandyClientHostname\n";
		#	print Dumper @IOReadFromClient;

			$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string($Status) . CRLF);
			close $HandyClientSocket;
		#	$LOGGING->LogToFileInvalidLicenceDistr( \$PlainText, $HandyClientHostname );	
			exit(0);

		}; # if ( $Status != 1 ) {}


		# Abschnitt Verbindungsaufbau
		# erstelle den socket zum paris programm 
		
		# dont modify this value!
		my $COUNT_READ_ONLY = keys( %ParisHosts ) - 1;
		my $COUNT			= 0;

		# Setzte hier eine Datei exclusive Sperrung des DB Fkat Files hinter %ParisSocketsHash durch
		flock(%ParisSocketsHash,LOCK_EX);

		while( my ($host,$hostname) = each(%ParisHosts)){

			#	next if ( $COUNT > $ParisHostCount );			# nächsten eintrag in schleife aufrufen
			last if ( $COUNT > $ParisHostCount );				# schleife abbrechen

			$ParisSocketsHashRef->{$COUNT.$MD5}->{'HOST'}					= $host;			# diese werte werden im verlauf genutzt
			$ParisSocketsHashRef->{$COUNT.$MD5}->{'HOSTNAME'}				= $hostname;
			
		#	$ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'}	= 0;

			# wenn ich an jedes Hash in connections.db noch eine unique id ranhänge, dann ist diese version multiconnect-fähig, 
			# da zu jeder IP der MD5 gebildet wird und dieser MD5 unique im Hash dann genutzt wird
			
			#	$ParisSocketsHash{'HOST'.$COUNT.$MD5}			= $host;			# diese werte werden im verlauf nicht genutzt
			#	$ParisSocketsHash{'SOCKET'.$COUNT.$MD5}			= $ParisSocket;

			$COUNT++;

		}; # while( my($host,$port) = each(%ParisHosts)){	

		# hebe hier eine Datei exclusive Sperrung des DB Fkat Files hinter %ParisSocketsHash wieder auf
		flock(%ParisSocketsHash, LOCK_UN);

		##########
		#### BitJoe Protokoll Request Verarbeitung
 		##########

		if ( $IOReadFromClient[0] eq 'find' ) {	# Abschnitt Suchanfrage stellen
		
			my $CurTime	= $TIME->MySQLDateTime();
			printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Submitting Search '$IOReadFromClient[5] .$IOReadFromClient[1]' to Phex on " . join(", ", keys(%ParisHosts)) . " \n";
			
			#### Verteile die Anfragen an alle Phex Proxys

			# Erstelle die CheckSumme für einen eingehenden Request - zusammengesetzt aus entsprechenden Werten
			my $ClientID = $CHECK->MD5ToHEX( $HandyClientHostname . time() . $CRYPTO->SimpleRandom() . $CRYPTO->URandom(10000) . $#IOReadFromClient ); 

			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

				my $ParisHost	= $ParisSocketsHashRef->{$COUNT.$MD5}->{'HOST'};				# ACCESS A HASHREF	
				$PHEX->find( $ParisHost, "$IOReadFromClient[5] .$FileTypeHashRef->{1}", $IOReadFromClient[1], $ClientID, $Token);
				
			}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

			$IO->writeSocket( $HandyClientSocket , $GZIP->gzip_compress_string( $ClientID ) . CRLF );	# immer Gültig, da MD5ToHEX immer 32 Zeichen zurückliefert
			
			unlink "$FILE/$ClientID";
			$IO->WriteFile( "$FILE/$ClientID", "$IOReadFromClient[5] .$FileTypeHashRef->{1}" );	# merke die den suchbegriff für die HandyID $ClientID
	
			# Logge Anfrage mit
		#	$LOGGING->LogToFileInit( \@IOReadFromClient );
		#	printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Submitting Search '$IOReadFromClient[5] .$IOReadFromClient[1]' to Phex with Client ID $ClientID \n";


		} elsif ( $IOReadFromClient[0] eq 'result' ){	# Abschnitt Ergebiss abholen
	
			# todo: wenn ein host schon ergebnisse geliefert hat, der andere noch nicht, dann nochmal so lange ca 4 mal den anderen zum abholen bewegen!!!
			my $CurTime				= $TIME->MySQLDateTime();
			my $StartExecutionTime	= $CurTime;

			printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Serving Request for Query: '$IOReadFromClient[5]' from all Servers " . join(", ", keys(%ParisHosts)) . " \n";
		
			my $ClientID						= $IOReadFromClient[6]; chomp($ClientID);
			my $tmpSearch						= $IO->ReadFileIntoScalar( "$FILE/$ClientID" );
			my $SEARCH							= ${$tmpSearch};
			my $CurTime							= $TIME->MySQLDateTime();
			my $UnparsedResultsFromAllServer	= "";
			my $UnparsedResultsFromFastestServ	= "";

			# Stelle die Result-Anfrage an jeden Paris Server: Start 
			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
				
				my $ParisHost			= $ParisSocketsHashRef->{$COUNT.$MD5}->{'HOST'};
				my $ParisHostName		= $ParisSocketsHashRef->{$COUNT.$MD5}->{'HOSTNAME'};
				my $DownloadResults		= $PHEX->result( $ParisHost, $ClientID, $Token);	
				my $TempStringLength	= length($DownloadResults);
		
				if ( $TempStringLength >= 71 ) {	# Hier wurde erfolgreich die result() Funktion aufgerufen und sie hat gültigen Content zurückgeliefert 
					
					# Vorbereitung - Ergebnisse vom schnellsten Host sollen zuerst bearbeitet werden 
					if ( $ParisHost ne $FastestDistributedServer ){
						$UnparsedResultsFromAllServer .= $DownloadResults;

					} elsif ( $ParisHost eq $FastestDistributedServer ){
						$UnparsedResultsFromFastestServ .= $DownloadResults;
					
					}; # if ( $ParisHost ne $FastestDistributedServer ){

					my $TempResultKbytes	= sprintf("%.2f", ( $TempStringLength / 1024 ));
					printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Successfully Served '$TempResultKbytes' Kbytes from $ParisHost | $ParisHostName \n";
					
				#	open(LOG,"+>>resultlog.txt");
				#		flock(LOG, LOCK_EX);
				#		print LOG "[ Connect from $HandyClientHostname at $CurTime ] - Successfully Served '$TempResultKbytes' Kbytes from $ParisHost | $ParisHostName for Search $SEARCH\n";
				#		flock(LOG ,LOCK_UN);
				#	close LOG;
											
				}; # if ( length($TempString) >= 10) {
					
			#	open(LOG,"+>>resultlog.txt");
			#		print LOG "#####################################################################################\n";
			#	close LOG;

				$TempStringLength = 0;	# resette String Länge

			}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
			# Stelle die Result-Anfrage an jeden Paris Server: Ende


			# Ergebnisse vom Schnellsten Server sollen zuerst ausgewertet werden
			if ( length($UnparsedResultsFromFastestServ) >= 71 ) {
				
				my $TmpResultString				= $UnparsedResultsFromAllServer;
				$UnparsedResultsFromAllServer	= "";
				$UnparsedResultsFromAllServer	.= $UnparsedResultsFromFastestServ . $TmpResultString;
			
			}; # if ( length($UnparsedResultsFromFastestServ) >= 71 ) {

			
			#####
			### result cutter: intelligent cut of results to $MaxRawDataToProcess Kbytes
			#	my $UnparsedResultsFromAllServer	= IntelligentRawDataCut( \$UnparsedResultsFromAllServer, $MaxRawDataToProcess);
			#	my $SortedResultsArrayRef			= $SORTRANK->PhexSortRank( $UnparsedResultsFromAllServer, $IOReadFromClient[5], $FileTypeHashRef );	# unparsed content - suchbegriff - filetyp
			#####

			my $SortedResultsArrayRef	= $SORTRANK->PhexSortRank( \$UnparsedResultsFromAllServer, $IOReadFromClient[5], $FileTypeHashRef );	# unparsed content - suchbegriff - filetyp
		
			if ( $SortedResultsArrayRef->[0] eq "-3333###-3333###-3333###-3333\n" ) {

				# Hier wurde erfolglos die result() Funktion aufgerufen und sie hat keinen gültigen Content zurückgeliefert ...
				$ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'}	+= 1;

			}; # if ( length($UnparsedResultsFromAllServer) < 200 ) {

			my $SendString				= "";							# Dies ist der String mit den finalen Ergebnissen für das Handy	
			my $RC						= 1;							# Anzahl der ausgelieferten Ergebnisse
			my $FinalResultCount		= @{$SortedResultsArrayRef};	# Anzahl aller Ergebnisse


			########
			### DEBUG: START
			########

	#		open(RESULT,">raw.txt");
	#			my $tmp = $UnparsedResultsFromAllServer;
	#			$tmp =~ s/\\n/\n/ig;
	#			$tmp =~ s/\;\!\$\#\%\&/   /ig;
	#			print RESULT $tmp;
	#		close RESULT;
	#
	#		open(RESULT,">result.txt");
	#		
	#		my $RCD = 1;
	#		# folgende foreach schleife setzt die ergebnisse für das handy zusammen
	#		foreach my $entry ( @{$SortedResultsArrayRef} ) {
	#			
	#			my ( $RANK, $PEERHOST, $SIZE, $SHA1 ) = split('###', $entry );
	#			
	#			chop($SHA1) if ( length($SHA1) == 33 );	# fix für PhexProxy::SortArray->Sort_Table();
	#			next if ( length($RANK) <= 0 || length($SIZE) <= 0 || length($SHA1) != 32 || length($PEERHOST) <= 0); 
	#			
	#			my $SizeKB	= ( $SIZE / 1024 );
	#			my $SizeMB	= ( $SizeKB / 1024 );
	#			$SizeKB		= sprintf ("%.2f", $SizeKB);
	#			$SizeMB		= sprintf ("%.2f", $SizeMB);
	#			my (@Src)	= split("\r\n", $PEERHOST );	my $SOURCES = @Src;
	#			my $Rounded = sprintf("%.0f", $RANK );
	#			print RESULT "\n ############ \n";
	#			print RESULT "RESULT: $RCD\n";
	#			print RESULT "DEBUG: RANK    : '$RANK' || $Rounded \n";
	#			print RESULT "DEBUG: SIZE    : '$SIZE' Bytes und $SizeKB KB| $SizeMB MB \n";
	#			print RESULT "DEBUG: SHA1    : '$SHA1' \n";	
	#			print RESULT "DEBUG: HOST    : '$PEERHOST' \n";
	#			print RESULT "DEBUG: SOURCES : '$SOURCES' \n";
	#			print RESULT " ############ \n";
	#
	#			# bei Ergebnissauslieferung --> truncated Points to int
	#			# $RANK = ceil($RANK);
	#			$RANK = sprintf("%.0f", $RANK );
	#			$RCD++;
	#
	#		};	# foreach my $entry ( @{$SortedResultsArrayRef} ) {}
	#
	#		close RESULT;

			########
			### DEBUG: END
			########

			
			# folgende foreach schleife setzt die ergebnisse für das handy zusammen
			foreach my $entry ( @{$SortedResultsArrayRef} ) {
				
				last if ( $SortedResultsArrayRef->[0] eq "-3333###-3333###-3333###-3333\n" );	# dieser String bedeutet keine Ergebnisse
				my ( $RANK, $PEERHOST, $SIZE, $SHA1 ) = split('###', $entry );
				
				chop($SHA1) if ( length($SHA1) == 33 );	# fix für PhexProxy::SortArray->Sort_Table();
				next if ( length($RANK) <= 0 || length($SIZE) <= 0 || length($SHA1) != 32 || length($PEERHOST) <= 0); 
				last if ( $RC > $NUMBEROFRESULTS );
	
				my $SizeKB	= ( $SIZE / 1024 );
				my $SizeMB	= ( $SizeKB / 1024 );
				$SizeKB		= sprintf ("%.2f", $SizeKB);
				$SizeMB		= sprintf ("%.2f", $SizeMB);
				my (@Src)	= split("\r\n", $PEERHOST );	my $SOURCES = @Src;
				my $Rounded = sprintf("%.0f", $RANK );

				print "\n ############ \n";
				print "RESULT: $RC\n";
				print "DEBUG: RANK    : '$RANK' || $Rounded \n";
				print "DEBUG: SIZE    : '$SIZE' Bytes und $SizeKB KB| $SizeMB MB \n";
				print "DEBUG: SHA1    : '$SHA1' \n";	
			#	print "DEBUG: HOST    : '$PEERHOST' \n";
				print "DEBUG: SOURCES : '$SOURCES' \n";
				print " ############ \n";
	
				# bei Ergebnissauslieferung --> truncated Points to int
				# $RANK = ceil($RANK);
				$RANK = sprintf("%.0f", $RANK );

				# NEW: 
				# $SendString .= $RANK .SEPARATOR. $SIZE .SEPARATOR. $SHA1 .SEPARATOR. $PEERHOST .SEPARATOR. "\t\t";
				$SendString .= $RANK .SEPARATOR. $SIZE .SEPARATOR. $SHA1 .SEPARATOR. $PEERHOST .SEPARATOR. "\r";
	
				$RC++;
	
			};	# foreach my $entry ( @{$SortedResultsArrayRef} ) {}
	
	

			# Öffne die ResultCache DB
			tie(%ParisResultCache, 'DB_File', $ResultCacheFile, O_RDWR|O_CREAT ) || warn "can't open $ResultCacheFile: $!";
			
			my $SendStringLength							= length($SendString);
			my $CacheEntry									= lc("$IOReadFromClient[5] .$IOReadFromClient[1]");		# dies ist unser cache hash key, zb. "paris .b0"
			my $CacheEntryCreateTime						= $CacheEntry . "-created";								# dies ist unser cache timestamp hash key

			# wenn der inhalt von sendstring größer ist als die länge des cache eintrages oder sendsting größer als 300 zeichen ist			
			# if ( $SendStringLength >= length($ParisResultCache{$CacheEntry}) && exists($ParisResultCache{$CacheEntry}) || $SendStringLength >= 70 ) {	
			if ( $SendStringLength >= 70 || $SendStringLength > length($ParisResultCache{$CacheEntry}) ) {	
				
				flock(%ParisResultCache,LOCK_EX);
				$ParisResultCache{$CacheEntry}				= $SendString;
				$ParisResultCache{$CacheEntryCreateTime}	= time();
				flock(%ParisResultCache, LOCK_UN);

				my $SendStringLengthKbytes					= sprintf("%.2f", ( $SendStringLength / 1024 ) );
				print "Successfully wrote '$SendStringLength' Bytes || '$SendStringLengthKbytes' Kbytes of Cache Data for Entry '$CacheEntry' \n";

			} else {	# speichere, wie oft schon erfolglos ergebnisse ausgeliefert wurden
				
				print "Failed writing '$SendStringLength' Bytes of NON Cache Data for Entry '$CacheEntry' \n";
				$ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'}	+= 1;
		
			}; # if ( length($SendString) >= length($ParisResultCache{$CacheEntry}) || length($SendString) >= 300 ) {	

			#### END: Setze die Ergebnisse zusammen, nimm dazu von jedem Paris Ergebnishost die Liste 


			############################################
			### Caching Related Debug Messages: Start

			my $NoResultCounter			= $ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'} || 0 ;
			my $CacheCreateTime			= $ParisResultCache{$CacheEntryCreateTime} || 0;	# wann wurde cache erstellt oder 0 setzen - dh cache existiert nicht
			my $CacheDifferTime			= time() - $CacheCreateTime;						# differenz zwischen aktuellem zeitpunkt und dem punkt der erstellung des caches
			my $CurTime					= $TIME->MySQLDateTime();

			my $UnparsedContentLength	= length($UnparsedResultsFromAllServer);
			my $UnparsedContentKbytes	= sprintf("%.2f", ( $UnparsedContentLength / 1024 ) );
						
			print "############### DEBUG CACHING START: ########### \n";
			print "CacheCreateTime	 : $CacheCreateTime \n";
			print "CacheDifferTime	 : $CacheDifferTime\n";
			print "NoResultTryFlag	 : $NoResultCounter\n";
			print "MaxNoResultTry	 : $MaxNoResultTries\n";
			print "HandyResultStr   : $SendStringLength Bytes\n";
			print "UnparsedContLeng : $UnparsedContentLength Bytes | $UnparsedContentKbytes Kbytes\n";
			print "Start Exec Time  : $StartExecutionTime\n";
			print "End Exec Time    : $CurTime\n";
			print "############### DEBUG CACHING END ########### \n";
		
			# wenn mehr als $MaxNoResultTries erfolglos versucht wurde ein ergebniss auszuliefern und der Cacheeintrag $ParisResultCache{$CacheEntry} existiert 
			# der cache zeittechnisch noch gültig ist, dh unter $MaxCacheValidTime sek liegt und die länge des Cache Eintrages $ParisResultCache{$CacheEntry} ok ist dann versuche einen cache hit
			if ( $NoResultCounter >= $MaxNoResultTries && $CacheDifferTime <= $MaxCacheValidTime && exists($ParisResultCache{$CacheEntry}) && length($ParisResultCache{$CacheEntry}) >= 50 && $SendStringLength <= 50 ) {
				
				my $CurTime			= $TIME->MySQLDateTime();
				$SendString			= $ParisResultCache{$CacheEntry};
				$SendStringLength	= length($SendString);
				print "[ Connect from $HandyClientHostname at $CurTime ] - End Of Transmission: Successful Serve Cache hit for Query: '$CacheEntry' \n\n";

			} else {
				
				my $CurTime	= $TIME->MySQLDateTime();
				print "[ Connect from $HandyClientHostname at $CurTime ] - End Of Transmission: Served Normal Phex Result for Query: '$CacheEntry' \n\n";

			}; # if ( $NoResultCounter >= $MaxNoResultTries && $CacheDifferTime <= $MaxCacheValidTime && length($SendString) <= 50 ) {

			### Caching Related Debug Messages: End
			############################################


			# schreibe dem handyclient den gezippten Ergebnis string!!!!
			if ( $SendStringLength >= 30 ) {
				
				# Sende dem Handy den gezippten Result String
				$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string($SendString) . CRLF );
			
				for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
					
					my $ParisHost = $ParisSocketsHashRef->{$COUNT.$MD5}->{'HOST'};
					$PHEX->del( $ParisHost, $ClientID, $Token );
					
				}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

				# lösche File mit client ID
				unlink "$FILE/$ClientID";
				delete $ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'};
				delete $ParisSocketsHashRef->{$HandyClientHostname};
			
			} else {	# keine Treffer

				my $NoResultTryFlag = $ParisSocketsHashRef->{$HandyClientHostname}->{'NORESULTTRY'};

				if ( $NoResultTryFlag == 2 ) {
				
					for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
						
						my $ParisHost = $ParisSocketsHashRef->{$COUNT.$MD5}->{'HOST'};
						$PHEX->del( $ParisHost, $ClientID, $Token );
						print "WARNING 2.Versuch keine results: loesche eintraege für $SEARCH bei $ParisHost \n";

					}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

				}; # if ( $NoResultTryFlag == 1 ) {

				# Sende dem Handy, dass noch keine Ergebnisse vorhanden sind
				$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string("FC 808") . CRLF );

			}; # if ( length($SendString) >= 30 ) {


		#	$LOGGING->LogToFileGetResults( \@IOReadFromClient, $ClientID );	


		} elsif ( $IOReadFromClient[0] eq 'dlstartlog' ){ 
			
			# Distr
			my $CurTime	= $TIME->MySQLDateTime();
			printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Download Start Request for Query: '$IOReadFromClient[5]' \n";	
		#	$LOGGING->LogToFileStartDownloadDistr( \$PlainText, $HandyClientHostname );	

		} elsif ( $IOReadFromClient[0] eq 'dlendlog' ){
			
			my $CurTime	= $TIME->MySQLDateTime();
			printf STDOUT "[ Connect from $HandyClientHostname at $CurTime ] - Download Finish Request for Query: '$IOReadFromClient[5]' \n";	
		#	$LOGGING->LogToFileFinishDownloadDistr( \$PlainText, $HandyClientHostname );	

		}; # if ( $ReadFromClient[0] eq 'find' ) { # } elsif ( $IOReadFromClient[0] eq 'result' ){
		
	
		###### Lösche bestehenden Einträge
		for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
			delete $ParisSocketsHashRef->{$COUNT.$MD5};		# hashref eintrag löschen
		}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

		# schließe die ResultCache DB, lösche NoResultFile und beended das Programm
		untie(%ParisResultCache);
		exit(0);

	}; #  unless ($child) {}

	# schließe HandySocket und die Connections Datenbank
	close $HandyClientSocket;
	untie(%ParisSocketsHash);
	#exit(0);

}; # while (!$DONE) {}

########################
##### while accept loop
########################

die "$0 - Normal termination\n";
exit(0);

### my $ParisSocket = $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};			# ACCESS A HASHREF
### DOESNT WORK! my $ParisSocket = $ParisSocketsHash{'SOCKET'.$COUNT};		# Access a HASH



sub IntelligentRawDataCut(){

	####
	## Intelligentes Beschneiden des Phex Contents - es wird so abgeschnitten, dass ein Ergebnis intakt bleibt an der zu cuttenen Stelle
	####

	my $FileContentRef		= shift;						# string Scalar ref
	my $MaxRawDataToProcess	= shift;						# long int
	
	my $FileContent			= ${$FileContentRef};			# greife mittels zeiger auf den inhalt hinter der referenz $FileContentRef zurück
	my $FileContentLength	= 0;							# long int
	my $FileContentLength	= length($FileContent);			# long  int
	my @SplittedContent		= ();							# array prototyp
	my $SplittedContent		= "";							# string

	if ( $MaxRawDataToProcess >= $FileContentLength ) {		# MaxDataToProcess ist größer als PhexContent - wir brauchen den datenstrom nicht abschneiden
		
		# print "DEBUG: No need to cut\n";
		return \$FileContent;								# Referenz auf String zurückgeben

	} elsif ( $FileContentLength > $MaxRawDataToProcess ) { # MaxDataToProcess ist kleiner als PhexContent - wir sollen den datenstrom abschneiden 
	
		(@SplittedContent)	= split("", $FileContent);		# Ein Array mit allen Zeihen des FileContent füllen

		for (my $i=$MaxRawDataToProcess; $i<=$FileContentLength; $i++ ) {
			
			my $SingleString		= $SplittedContent[$i];
			my $SingleStringNext	= $SplittedContent[$i+1];
			
			if ( $SingleString eq "\\" && $SingleStringNext eq "n" ) {
				
				my $IntelligentCutAtBytePosition = $i + 2;	# aktuelle position plus 2 zeichen '\n'
				$FileContent = substr($FileContent, 0, $IntelligentCutAtBytePosition);	# Intelligent Cut

				# print "DEBUG: I cutted at $IntelligentCutAtBytePosition instead of $MaxRawDataToProcess \n"; 
				return \$FileContent;

			}; # if ( $SingleString eq "\\" && $SingleStringNext eq "n" ) {

		}; # for (my $i=$MaxRawDataToProcess; $i<=$FileContentLength; $i++ ) {
	
	}; # if ( $FileContentLength <= $MaxRawDataToProcess ) {

	return \$FileContent;	# never reached

}; # sub IntelligentRawDataCut(){


#### Achtung: hier erstmal alles auskommentiert lassen, da sonst bei keinen Ergebnissen zu lange gewartet wird, bis FC 808 gesendet wird
				#	# ... darum hier noch $MaxRetryOnFailedIORead Mal versuchen Content abzuholen
				#	for ( my $x=0; $x<=$MaxRetryOnFailedIORead; $x++) {
				#	
				#		last if ( length($DownloadResults) >= 50 );
				#		my $RandSleepTime = rand($MaxRetryOnFailedIOSleep);
				#		select (undef, undef, undef, $RandSleepTime );	
				#
				#		my $DownloadResults	= $PHEX->result( $ParisHost, $ClientID, $Token);	
				#		$UnparsedResultsFromAllServer	.= $DownloadResults;
				#
				#	}; # for ( my $x=0; $x<=$MaxRetryOnFailedIORead; $x++) {
				#
				#	# speichere, dass schon einmal erfolglos versucht wurde ergebnisse zu holen
				#	# Eintrag zeigt Anzahl erfolgloser Versuche zur Auslieferung eines Caches an