#!/usr/bin/perl -I/server/phexproxy/PhexProxy

use strict;
# use warnings;

#########################################
##### Author:		Sebastian Enger / B.Sc
##### CopyRight:	BitJoe GmbH
##### LastModified	5.03.2007
##### Function:		Hauptdatei für PhexProxy
##### Todo:			for statt foreach: ist schneller
##### Hint:			minimum StringLength of one Result is about 50-70 Chars long
########################################

# root: -20 <> 20 | normal: 0 <> 20
my $ServicePriority = "-1";				

system("renice $ServicePriority $$");
system("clear");

### TODO: Encoding unicode  / zeile 499 	print "MaxNoResultTry: $MaxNoResultTries\n"; bug beheben,. zählt nicht richtig hoch
### in paris.pl add phex alive check, und dann die daten an den distributed weitergeben, wenn server down
### todo: Z284	# hier einbauen, dass 3-5 mal versucht werden soll vom host ergebnisse abzuholen

use DB_File;
use IO::Select;
use POSIX qw(ceil);
use Data::Dumper;
use PhexProxy::IO;
use PhexProxy::Mail;
use PhexProxy::Gzip;
use PhexProxy::Time;
use PhexProxy::Daemon;
use PhexProxy::Logging;
use PhexProxy::CheckSum;
use PhexProxy::CryptoLibrary;
use PhexProxy::LicenceManagement;
use PhexProxy::SortArray qw( Sort_Table );


# definiere die paris host server hier, die angefragt werden sollen
my %ParisHosts				= (
	"87.106.63.182"		=> "3385",
	"81.169.141.129"	=> "3385",
	"85.214.39.76"		=> "3385",
	"85.214.122.130"	=> "3385",
);


my $HandyClientIPAdress;
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 $MaxResults				= 15000;		# 10 Ergebnisse für den HandyClient
my $MaxSourcesForOneResult	= 25000;		# Max Quellen pro Ergebnis
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	= 18;		# wait max of time for results for while read ParisResult socket!
my $ParisHostCount			= keys( %ParisHosts );

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 ($ServerIPAdress)		= `LANG=US_us ifconfig eth0`=~/inet addr:([\d.]+)/;

use constant VERSION		=> "PhexDistributedProxy 0.9.9.7 - 28.11.2007 @ 15.25 Uhr";
use constant PIDFILE		=> "/server/phexproxy/phexdistributedproxygzip-pid.txt";

use constant CRLF			=> "\r\n";
use constant SEPARATOR		=> "#";				# former '##'
use constant LINEEND		=> "\r";

my $IO						= PhexProxy::IO->new();
my $GZIP					= PhexProxy::Gzip->new();
my $MAIL					= PhexProxy::Mail->new();
my $TIME					= PhexProxy::Time->new();
my $LOGGING					= PhexProxy::Logging->new();
my $CHECK					= PhexProxy::CheckSum->new();
my $CRYPTO					= PhexProxy::CryptoLibrary->new();
my $LICENCE					= PhexProxy::LicenceManagement->new();
my $Socket					= PhexProxy::IO->CreateHandyDistributedSocket( $port ) || $MAIL->SendNoHandyClientSocketMail( $port );
my $IN						= IO::Select->new($Socket);

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 Priority '$ServicePriority']\n";

# signarl 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) || die "can't open $DBFlatFile: $!";

	next unless $IN->can_read;
	next unless $HandyClientSocket	= $Socket->accept();
	$HandyClientIPAdress			= $HandyClientSocket->sockhost;
	my $MD5							= $CHECK->MD5ToHEX($HandyClientIPAdress);

	my $child						= launch_child();
	
	unless ($child) {

		close $Socket;

		# wenn load zu hoch ist
		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 
			# $IO->writeSocket( $HandyClientSocket, "FC 909" . CRLF ); # FC 909: load auf dem server zu hoch 
			close $HandyClientSocket;
			exit(0);

		}; # if ( $CurrentLoad > $MaximumAllowedLoadForPhex ) {


		# erstelle den socket zum paris programm 
		my $COUNT = 0;

		while( my ($host,$port) = each(%ParisHosts)){

			next if ( $COUNT > $ParisHostCount );
			my $ParisSocket = PhexProxy::IO->CreateSocketToParis( $host,$port );

			if ( ref($ParisSocket) eq "IO::Socket::INET" ) { 
			
				$ParisSocketsHashRef->{$COUNT}->{'HOST'}		= $host;			# diese werte werden im verlauf genutzt
				$ParisSocketsHashRef->{$COUNT}->{'SOCKET'}		= $ParisSocket;
				
				# 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++;

			} else {
				
				print "\t Warning Paris Backend '$host' is down!\n";
				$MAIL->SendProxyDownMail($host);
				
			}; # if ( ref($ParisSocket) eq "IO::Socket::INET" ) { 

		 }; # while( my($host,$port) = each(%ParisHosts)){	


		# dont modify this value!
		my $COUNT_READ_ONLY = keys( %ParisHosts ) - 1;


		# print Dumper %ParisSocketsHash; 
		# print $ParisSocketsHash{'HOST'.$COUNT}; exit;

		# Nehme den Content vom Handy an
		
		my $CurTime				= $TIME->MySQLDateTime();
		my $ReadFromClient		= $IO->readSocket( $HandyClientSocket );
		my $ReadFromClientOrg	= $ReadFromClient;
		my @ReadFromClient		= split('##', $ReadFromClient );
	

		####################### 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
			exit(0);

		}; # if ( $PrivateKey == -1 ) {}


		my $PlainText			= $CRYPTO->Decrypt( $PrivateCryptoKey, $ReadFromClient[2] );
		my @IOReadFromClient	= ();		# resette das wichtige Array
	
		my ( $stuff	)			= split("\r\n", $PlainText ); 
		@IOReadFromClient 		= split("##", $stuff ); 


		############### Licence checken - Start ################
		my ( $Status, $FileTypeHashRef ) = $LICENCE->CheckLicence( \@IOReadFromClient );

		if ( $Status != 1 ) {
			
			print "CheckLicence failed - from: IP $HandyClientIPAdress\n";
			
			$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string($Status) . CRLF);
			close $HandyClientSocket;
			$LOGGING->LogToFileInvalidLicenceDistr( \$PlainText, $HandyClientIPAdress );	
			exit(0);

		}; # if ( $Status != 1 ) {}
		############### Licence checken - Ende ################


		if ( $IOReadFromClient[0] eq 'find' ) {
		
			printf STDOUT "[ Connect from $HandyClientIPAdress at $CurTime ] - Submitting Search '$IOReadFromClient[5] .$IOReadFromClient[1]' to Phex on " . join(", ", keys(%ParisHosts)) . " \n";
			
			#### Verteile die Anfragen an alle Phex Proxys
			
			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

				my $ParisSocket = $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};	# ACCESS A HASHREF	
				next if ( ref($ParisSocket) ne "IO::Socket::INET" );

				$IO->writeSocket( $ParisSocket , $ReadFromClientOrg );								# suchanfrage des handys direkt an paris.pl weiterreichen	
				my $CompressedQueryID						= $IO->readSocket( $ParisSocket );		# komprimierte query id abholen ...
				my $OriginalCompressedQueryID				= $CompressedQueryID;
				my $DeCompressedQueryID						= $GZIP->gzip_decompress_string( $CompressedQueryID );
				$ParisSocketsHash{'QUERYID'.$COUNT.$MD5}	= $DeCompressedQueryID;					# speichere den QueryID
				$IO->writeSocket( $HandyClientSocket , $OriginalCompressedQueryID );				# ... und an das Handy weitereichen

			}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

			# Lösche die NoResultFileCount Datei 
			unlink "$TmpFilePath/$MD5";

			# schreibe, dass schon 0 mal erfolglos versucht wurden, ergebnisse abzuholen
			$IO->WriteFile("$TmpFilePath/$MD5", 0 );


		} elsif ( $IOReadFromClient[0] eq 'result' ){
	
			# todo: wenn ein host schon ergebnisse geliefert hat, der andere noch nicht, dann nochmal so lange ca 4 mal den anderen zum abholen bewegen!!!

			printf STDOUT "[ Connect from $HandyClientIPAdress at $CurTime ] - Serving Request for Query: '$IOReadFromClient[5]' from " . join(", ", keys(%ParisHosts)) . " \n";
			
			# Stelle die Result-Anfrage an jeden Paris Server
			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
				
				####################### crypto api implementatione #####################
				#	$IOReadFromClient[0] = MD5 Wert der Verschlüsselten Nachricht
				#	$IOReadFromClient[1] = der public schlüssel 
				#	$IOReadFromClient[2] = die verschlüsselte nachricht

				my $ParisSocket							= $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};
				my $ParisSocketHost						= $ParisSocketsHashRef->{$COUNT}->{'HOST'};

				next if ( ref($ParisSocket) ne "IO::Socket::INET" );

				my $QueryID								= $ParisSocketsHash{'QUERYID'.$COUNT.$MD5};
				$IOReadFromClient[6]					= $QueryID;
				my $FinalResultString					= join("##", @IOReadFromClient );
				$FinalResultString						.= CRLF;
		
				my $PublicCryptoKey						= $ReadFromClient[1];
				my $CryptoText							= $CRYPTO->Encrypt( $PrivateCryptoKey, $FinalResultString );
				my $CryptoTextMD5						= $CHECK->MD5ToHEX($CryptoText);
				my $FinalResultSendString				= "$CryptoTextMD5##$PublicCryptoKey##$CryptoText" . CRLF;
					
				$IO->writeSocket( $ParisSocket , $FinalResultSendString );		# suchanfrage des handys an paris.pl weiterreichen	
	
				my $TempString = ""; my $buffIO = "";
				

				# hier einbauen, dass 3-5 mal versucht werden soll vom host ergebnisse abzuholen
				eval {
					
					local $SIG{'ALRM'} = sub { warn "Paris '$ParisSocketHost' not serving results in $MaxResultServeTimeout seconds \n"; sleep 4; goto ALARMLABEL; };
					alarm $MaxResultServeTimeout;
					
					while (sysread($ParisSocket, $buffIO, 1) == 1) {
						$TempString .= $buffIO;
					}; # while
					
					# alarm 0;

				}; # eval {

				$TempString		= $GZIP->gzip_decompress_string( $TempString );

				if ( length($TempString) => 100 ) {
					$ParisSocketsHash{'RESULTS'.$COUNT.$MD5} = $TempString;

				}; # if ( length($TempString) => 10) {

				ALARMLABEL:

			}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	


			#### START: Setze die Ergebnisse zusammen, nimm dazu von jedem Paris Ergebnishost die Liste und nimm davon genau 
			
			my @AllResultContent	= ();
			my $LineEndingMatch		= LINEEND;

			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
				
				my $TempResultString	= $ParisSocketsHash{'RESULTS'.$COUNT.$MD5};
				my @ResultContent		= split( "$LineEndingMatch", $TempResultString );
				my $TempResultStringLen	= length($TempResultString);
				my $TempResultKbytes	= sprintf("%.2f", ( $TempResultStringLen / 1024 ));
				my $Host				= $ParisSocketsHashRef->{$COUNT}->{'HOST'};
				
				print "Host: '$Host' served '$TempResultStringLen' Bytes || '$TempResultKbytes' Kbytes of Result Data\n"; 
				select(undef, undef, undef, 1.7);
				
				next if ( $#ResultContent <= 0 || $TempResultStringLen <= 50 );
				push(@AllResultContent, @ResultContent);
			
			}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
	

			# diese for schleife bringt die ergebnisse des arrays @AllResultContent in passende form für den Hash hinter $SortedHashRef

			my $SortedHashRefCount	= 0;
			my $SortedHashRef		= {};

			for ( my $i=0; $i<=$#AllResultContent; $i++ ) {
				
				my $content	= $AllResultContent[$i];
				$content	=~ s/^\s+//;
				$content	=~ s/\s+$//;
				my $strlen	= length($content);
				next if ( $strlen == 0 );	
						
				if ( $content =~ m/[#{3}]/ && $strlen > 40 ) {	# start of one result
					
					# '161#42189#2DLDVBTWNIIP2EJNRNKSGKU4MAXAZQDT#74.72.92.178:23310;644;paris.jpg'
					# print "[$strlen] Start of Entry: $content \n"; 

					my ( $RANK, $SIZE, $SHA1, $PEERHOST ) = split("#", $content );	
					
					if ( length($RANK) > 0 && length($SIZE) > 0 && length($SHA1) == 32 && length($PEERHOST) > 0 ) {
					
						$SortedHashRef->{ $SortedHashRefCount }->{ 'SZ' } = $SIZE;
						$SortedHashRef->{ $SortedHashRefCount }->{ 'SH' } = $SHA1;
						$SortedHashRef->{ $SortedHashRefCount }->{ 'RK' } = $RANK;
						$SortedHashRef->{ $SortedHashRefCount }->{ 'PH' } = $PEERHOST . CRLF;
						
					}; # if ( length($RANK) > 0 && length($SIZE) > 0 && length($SHA1) == 32 && length($PEERHOST) > 0 ) {

				} elsif ( $content =~ /((25[0-4]|(2[0-4]|1[0-9]|[1-9]?)[0-9]\.){3}(25[0-4]|(2[0-4]|1[0-9]|[1-9]?)[0-9]))+(\:)([0-9]{1,})/i && $strlen >= 15 ) {	# sources for one result hit
					
					# '70.83.169.134:6348;1002;paris.jpg'
					# print "[$strlen] Sources Hit: '$content' \n"; sleep 1; 
					
					$SortedHashRef->{ $SortedHashRefCount }->{ 'PH' } .= $content . CRLF;

				} elsif ( $content =~ m/[#{1}]/ && $strlen < 5 ) {	# end of result
					
					# '#'
					#	print "[$strlen] End of result \n";
					
					# ende eines ergebnisses, darum den HashCounter eins hochzählen
					$SortedHashRefCount++;

				}; # if ( $content =~ m/[#{3}]/ && $strlen > 40 ) {	# start of one result


			}; # for ( my $i=0; $i<=$#AllResultContent; $i++ ) {


			# in den folgenden 2 for schleifen werden die ergebnisse anhand gleicher SHA1 werte zusammengefasst und die quellen von den einzelnen Paris Ergebnisse ineinander integriert
			my $SortHashRefCount	= keys(%{$SortedHashRef});
		
			for ( my $Key=0; $Key<=$SortHashRefCount; $Key++ ) {

				my $SHA1			= $SortedHashRef->{ $Key }->{ 'SH' };
				my $Size			= $SortedHashRef->{ $Key }->{ 'SZ' };
				my $PeerHost		= $SortedHashRef->{ $Key }->{ 'PH' };
				my $Rank			= $SortedHashRef->{ $Key }->{ 'RK' };
				
				for ( my $NextKey=0; $NextKey<=$SortHashRefCount; $NextKey++ ) {

					my $SHA1Next		= $SortedHashRef->{ $NextKey }->{ 'SH' };
					my $SizeNext		= $SortedHashRef->{ $NextKey }->{ 'SZ' };
					my $PeerHostNext	= $SortedHashRef->{ $NextKey }->{ 'PH' };
					my $RankNext		= $SortedHashRef->{ $NextKey }->{ 'RK' };

					if ( ($SHA1 eq $SHA1Next) && length($SHA1) == 32 && length($SHA1Next) == 32 ) {

						my @PEERSNEXT	= split("\r\n",$PeerHostNext );
						my @PEERS		= split("\r\n",$PeerHost );
					
						# füge von Anzahl der Einträge in Peers bis zum Wert $MaxSourcesForOneResult noch werte aus PeerHostNext zu PeerHost dazu
						my $StartAtSource = 0;

						for ( my $i=$#PEERS; $i<=$MaxSourcesForOneResult; $i++ ) {
							
							# später next if ( $#PEERS >= $MaxSourcesForOneResult ); 
							# später: wenn $PEERSNEXT[$StartAtSource] kein gültiges Ergebnis liefert, dann nimm eins aus dem Array, dass eins liefert
							
							 if ( length($PEERSNEXT[$StartAtSource]) >= 20 && lc($SortedHashRef->{ $Key }->{ 'PH' }) ne lc($PEERSNEXT[$StartAtSource]) ){
								$SortedHashRef->{ $Key }->{ 'PH' } .= $PEERSNEXT[$StartAtSource] . "\r\n";
								$StartAtSource++;
							 };

						}; # for ( my $i=$#PEERS; $i<=$MaxSourcesForOneResult; $i++ ) {
					
						# schlage einen prozentualen teil der punkte von PeerNext auf Peer auf
						my $AddPointsToRank = 0;
						$AddPointsToRank	= ceil( $RankNext / $StartAtSource ) if ( $RankNext > 0 && $StartAtSource > 0 );		# ceil Runde die Punkte auf / Divison durch Null Bug- Lösung
						$SortedHashRef->{ $Key }->{ 'RK' } += $AddPointsToRank if ( $StartAtSource > 0 );

						# !!! Wichtig: in $SortedHashRef wird KEIN EINTRAG gelöscht, wir lösen das problem der 
						# eventuellen doppelten einträge später in der Result Ausgabe mit einem if exits HashRef->SHA1!

					}; # if ( ($SHA1 eq $SHA1Next) && length($SHA1) == 32 && length($SHA1Next) == 32 ) {

				}; # for ( my $NextKey=0; $NextKey<=$SortHashRefCount; $NextKey++ ) {

			}; # for ( my $Key=0; $Key<=$SortHashRefCount; $Key++ ) {


			# Öffne die ResultCache DB
			tie(%ParisResultCache, 'DB_File', $ResultCacheFile ) || warn "can't open $ResultCacheFile: $!";
			
			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

			# folgende foreach schleife setzt die ergebnisse für das handy zusammen

			my $SendString				= "";	# Dies ist der String mit den finalen Ergebnissen für das Handy	
			my $FinalResultCount		= 0;
			my $NoDoubleEntryHashRef	= {};

			my $WholeSources			= 0;	# gesamtanzahl der quellen
			
			foreach my $Key ( sort { $SortedHashRef->{$b}->{'RK'} <=> $SortedHashRef->{$a}->{'RK'} } keys %{$SortedHashRef} ) {	# sortiere nach Rank-ing Punkten
				
				my $SHA1			= $SortedHashRef->{ $Key }->{ 'SH' };
				my $Size			= $SortedHashRef->{ $Key }->{ 'SZ' };
				my $PeerHost		= $SortedHashRef->{ $Key }->{ 'PH' };
				my $Rank			= $SortedHashRef->{ $Key }->{ 'RK' };
				
				next if ( exists( $NoDoubleEntryHashRef->{$SHA1} ) );	# hier keine doppelten einträge(2 gleiche SHA1-werte) mehr anzeigen lassen
				next if ( $FinalResultCount > $MaxResults );			# überspringen, wenn schon mehr als $MaxResults Ergebnisse erstellt wurden
	
				if ( length($SHA1) == 32 && length($Size) > 0 && length($PeerHost) > 0 && length($Rank) > 0 ) {
					
					$SendString	.= $Rank .SEPARATOR. $Size .SEPARATOR. $SHA1 .SEPARATOR. $PeerHost .SEPARATOR. "\r";

					my $SizeKB		=  sprintf("%.2f", ( $Size / 1024 ) );
					my $SizeMB		=  sprintf("%.2f", ( $SizeKB / 1024 ) );
					
					my @PEERCOUNT	= split(":", $PeerHost );
					$WholeSources	+= $#PEERCOUNT;
					@PEERCOUNT		= ();

					print "Entry: $FinalResultCount \n";
					print "DEBUG: SIZE: '$Size' Bytes und $SizeKB KB| $SizeMB MB \n";
					print "DEBUG: RANK: '$Rank'\n";
					print "DEBUG: SHA1: '$SHA1'\n";
					print "DEBUG: PeerHost: '$PeerHost'\n";
					print "############ \n\n";

				}; # if ( length($SHA1) == 32 && length($Size) > 0 && length($PeerHost) > 0 && length($Rank) > 0 ) {

				$NoDoubleEntryHashRef->{$SHA1} = "";
				$FinalResultCount++;

			}; # foreach my $entry ( sort

			# debug: cache testing + noresult testing
			### $SendString = "";


			my $SendStringLength							= length($SendString);
			
			# 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 ) {	
		
				$ParisResultCache{$CacheEntry}				= $SendString;
				$ParisResultCache{$CacheEntryCreateTime}	= time();
				
				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
					
				my $TmpNoResultCounter			= $IO->ReadFileIntoScalar("$TmpFilePath/$MD5");
				my $TmpNoResultCounterScalar	= ${$TmpNoResultCounter};
				chomp($TmpNoResultCounterScalar);
				$TmpNoResultCounterScalar++;
				$IO->WriteFile("$TmpFilePath/$MD5", $TmpNoResultCounterScalar);
		
			}; # if ( length($SendString) >= length($ParisResultCache{$CacheEntry}) || length($SendString) >= 300 ) {	

			#### END: Setze die Ergebnisse zusammen, nimm dazu von jedem Paris Ergebnishost die Liste 


			##########################
			### Caching Related: Start

			my $TmpNoResultCounter		= $IO->ReadFileIntoScalar("$TmpFilePath/$MD5");
			my $NoResultCounter			= ${$TmpNoResultCounter};
			my $CacheCreateTime			= $ParisResultCache{$CacheEntryCreateTime};			# wann wurde cache erstellt
			my $CacheDifferTime			= time() - $CacheCreateTime;						# differenz zwischen aktuellem zeitpunkt und dem punkt der erstellung des caches
			$CurTime					= $TIME->MySQLDateTime();

			print "############### DEBUG CACHING START: ########### \n";
			print "CacheCreateTime: $CacheCreateTime \n";
			print "CacheDifferTime: $CacheDifferTime\n";
			print "NoResultTryFlag: $NoResultCounter\n";
			print "MaxNoResultTry: $MaxNoResultTries\n";
			print "SendStringLengh: $SendStringLength\n";	$FinalResultCount--;
			print "ResultCount: $FinalResultCount\n";
			print "WholeSourceCount: $WholeSources\n";
			
			for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
				
					my $TempStringLenght = length($ParisSocketsHash{'RESULTS'.$COUNT.$MD5});
					if ( $TempStringLenght => 200 ) {
						
						my $tempHost	= $ParisSocketsHashRef->{$COUNT}->{'HOST'};
						my $SizeKBTmp	= sprintf("%.2f", ( $TempStringLenght / 1024 ) );
						print "Host: $tempHost has served '$SizeKBTmp' KBytes\n";

					}; # if ( $TempStringLenght => 200 ) {

				}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
			

			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 ) {
				print "[ Connect from $HandyClientIPAdress at $CurTime ] - Serving Cache hit for Query: '$CacheEntry' \n";
				$SendString = $ParisResultCache{$CacheEntry};

			} else {
				print "[ Connect from $HandyClientIPAdress at $CurTime ] - Serving Normal Phex Result for Query: '$CacheEntry' \n" if ( length($SendString) >= 500 );

			}; # if ( $NoResultCounter >= $MaxNoResultTries && $CacheDifferTime <= $MaxCacheValidTime && length($SendString) <= 50 ) {

			### Caching Related: End
			########################


			# schreibe dem handyclient den gezippten string!!!!
			if ( $SendStringLength >= 30 ) {
				$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string($SendString) . CRLF );

			} else {
				$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string("FC 808") . CRLF );

			}; # if ( length($SendString) >= 30 ) {


			# resette die flat file tabelle  für den bestimmten und grade bearbeiteten eintrag, wenn mehr als 2 ergebnisse ausgeliefert wurden
			if ( $FinalResultCount >= 1 ) {

				for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
				
					$ParisSocketsHash{'HOST'.$COUNT.$MD5}			= "";	
					$ParisSocketsHash{'SOCKET'.$COUNT.$MD5}			= "";	
					$ParisSocketsHash{'QUERYID'.$COUNT.$MD5}		= "";	
					$ParisSocketsHash{'RESULTS'.$COUNT.$MD5}		= "";	
				
				}; # for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	
			
			}; # if ( $FinalResultCount >= 2 ) {


		} elsif ( $IOReadFromClient[0] eq 'dlstartlog' ){ 
			
			# Distr
			printf STDOUT "[ Connect from $HandyClientIPAdress at $CurTime ] - Download Start Request for Query: '$IOReadFromClient[5]' \n";	
			$LOGGING->LogToFileStartDownloadDistr( \$PlainText, $HandyClientIPAdress );	

		} elsif ( $IOReadFromClient[0] eq 'dlendlog' ){

			printf STDOUT "[ Connect from $HandyClientIPAdress at $CurTime ] - Download Finish Request for Query: '$IOReadFromClient[5]' \n";	
			$LOGGING->LogToFileFinishDownloadDistr( \$PlainText, $HandyClientIPAdress );	

		}; # if ( $ReadFromClient[0] eq 'find' ) { # } elsif ( $IOReadFromClient[0] eq 'result' ){
		
	
		###### Schließe alle Sockets und lösche bestehenden Einträge
			
		for ( my $COUNT=0; $COUNT<=$COUNT_READ_ONLY; $COUNT++ ){	

			$ParisSocketsHashRef->{$COUNT}->{'HOST'} = "";
			next if ( ref( $ParisSocketsHashRef->{$COUNT}->{'SOCKET'} ) ne "IO::Socket::INET" );
			close $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};

		}; # 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);

}; # while (!$DONE) {}

########################
##### while accept loop
########################

die "$0 - Normal termination\n";
exit(0);


############ Changelog: #######
# 14.11.2007:	added signal handler | alarm timeout while getting results from servers to avoid blocking of $0 
#				use warnings; compatible
# 15.11.2007:	fixed NoResultCheck | added debug messages | caching tested |

###############################

### my $ParisSocket = $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};			# ACCESS A HASHREF
### DOESNT WORK! my $ParisSocket = $ParisSocketsHash{'SOCKET'.$COUNT};		# Access a HASH
