#!/usr/bin/perl -w
use strict;
#use lib qw ( @@ LIBDIR@@ );
use lib qw ( /usr/lib/perl5/5.6.1/ );
# my $kas=/"/";
use Socket;

use Carp;
use Lstatobj;
use Misc qw ( %config $lastrun );
use File::stat;
use POSIX qw(setsid);
my $logfile = "/var/log/lstatd";
$ENV{'PATH'} = '/bin:/usr/bin:/sbin:/usr/sbin';

#read debug level
my $Debugging = 0;
if (@ARGV == 2) {
 $Debugging = $ARGV[1];
}

if ($Debugging < 3) {
#start daemon
chdir '/' or die "Can't chdir to /: $!";
#umask 0077;
umask 0;
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open (STDOUT, ">$logfile") or die "Can't write to $logfile: $!";
open (STDERR, '>&STDOUT') or die "Can't redirect STDERR to STDOUT: $!";

defined (my $pid=fork ) or die "Cant' fork: $!";
exit if $pid;
setsid or die "Can't start a new session: $!";


select(STDERR); $| = 1;     # make unbuffered
select(STDOUT); $| = 1;     # make unbuffered
}

$SIG {'CLD'} = "IGNORE";

$lastrun = -1;


my $MyConfig = \%config;
my $Forsleep = $config {'LIVE_PERIOD'};
my $socketfile = $config {'SOCKET'}; #to communication with client
#my $socketfile = '/tmp/test23'; #to communication with client
my $rin;
my $netmsg;

#create socket
 umask 0111;
 my $uaddr = sockaddr_un($socketfile);
 my $proto = getprotobyname('tcp');

 socket(Server,PF_UNIX,SOCK_STREAM,0) 	|| die "cant create socket: $!";
 #chmod (0700, $socketfile);
 unlink($socketfile);
 bind  (Server, $uaddr) 			|| die "cant bind socket: $!";
 listen(Server,SOMAXCONN)			|| die "cant listen: $!";

umask 002;

my @Objects = ();
my @LiveObjects = ();
#arrays objets to update
my %UpdateByPeriod  = ();
#load all objects
LoadAllObjects();

#main program loop
my $objtable;
my $obj;
my @MyLocalTime = localtime ();  # to save local time
my $MinCounter = 0; #count minutes

while () {
 my $ti = time;
 print "Sleep: $lastrun\n" if ($Debugging);
 $lastrun++; #synchronize with update procedures

 $rin ='';
 vec ($rin, fileno (Server),1)=1;
 #wait for connection or timeout
 if (select ($rin, undef, undef,$Forsleep)) {
  accept(Client,Server);
  
  
 # while (defined ( $netmsg=<Client>)) {
  #processing command
  if ($Debugging) { printdebuginfo ("Message found") }
  my $line;
  $line = <Client>; #read command from client
   chomp ($line);
   my ($com, $objid) = split (/\s/,$line);
   print "Command: $com $objid \n" if ($Debugging);
   if ($com == 0 ) {
   #create and update new objects
   my $obj2 = SafeLoad ($objid);
   if (defined ($obj2)) {
     if ($Debugging) { printdebuginfo ("Create object") }
     $obj2 -> CreateRRD (); #create new temp objects
     if (@LiveObjects >= $config {'MAX_LIVE' }){
     #too man live objects, destroy foldest object
     DestroyObj ($LiveObjects[0]->name(), \@LiveObjects);
     }
     push (@LiveObjects, $obj2);
    }
   } elsif ($com == 1) {
     #destroy object
     DestroyObj ($objid, \@LiveObjects);
   } elsif ($com == 2) {
     #create new rrd file
     my $obj2 = SafeLoad ($objid);
     if (defined ($obj2)) {
      push (@Objects, $obj2);
     #put again objects in period arrays
     CreateUpdateArray ();

     }
   }
   elsif ($com == 3) {
     #recreate rrd file
     RecreateRRD ($objid);
   }
  elsif ($com == 4) {
     #destroy objects
     DestroyObj ($objid, \@Objects);
     #put again objects in period arrays
     CreateUpdateArray ();
     }
  elsif ($com == 5) {
     #change value sleeping time
     $Forsleep = $objid;
     printdebuginfo ("Update live period to $Forsleep")     if ($Debugging);
     } #end com 5
  elsif ($com == 6) {
      #reload all objects
     printdebuginfo ("Reloading all objects")     if ($Debugging);
     LoadAllObjects();
     } #end com 6

 print Client "OK\n"; #send response to client
 close Client; #end comunication
  } #end found message



 #updating live objects
 if (@LiveObjects) {
  foreach my $obj2 (@LiveObjects) {
   if ($Debugging) {
    printdebuginfo (" Update ",$obj2->name ());
    }
   $obj2 -> UpdateRRD ();
  }
 }

 if (defined ($UpdateByPeriod {'SEC'})) {
  #update second
  $objtable = $UpdateByPeriod {'SEC'};
  foreach my $key (@$objtable) {
   if (($ti - $$key[2]) >= $$key[1]) {
   #need update
   $obj = $$key[0];
   if ($Debugging) {
    printdebuginfo (" Update ",$obj->name ());
    }
   #update objects data
   $obj -> UpdateRRD ();
   $$key[2] = $ti; #save last update time
   }
  }
 }


 my @ltime = localtime ($ti); #convert to local time
 if ($ltime [1] != $MyLocalTime [1]) {
 $MyLocalTime [1] = $ltime [1];
  #whole minute
  #update obj by MIN, HOUR ...
  UptadeSecondOBJ ('MIN');

  if ($MinCounter++ > 30 ) {
    $MinCounter = 0;
    #try destroy lost live objects
    map {TestLive ($_) } @LiveObjects if (@LiveObjects);
  }

  if ($ltime [2] != $MyLocalTime [2]) {
  $MyLocalTime [2] = $ltime [2];
   #whole hour
   UptadeSecondOBJ ('HOUR');

    #clean PNG dir 
    CleanPNGDir ();

   if ($ltime [3] != $MyLocalTime [3]) {
   $MyLocalTime [3] = $ltime [3];

    #new day
    UptadeSecondOBJ ('DAY');

    if ($ltime [4] != $MyLocalTime [4]) {
    $MyLocalTime [4] = $ltime [4];
     #new month
     UptadeSecondOBJ ('MONTH');
     }
     if (!$ltime [6]) {
     #new week - Sunday
     UptadeSecondOBJ ('WEEK');
     }
    }
   }
 }
}


#put objects in period arrays
sub CreateUpdateArray {

#free all objects
if (%UpdateByPeriod ) {
 foreach my $key (keys %UpdateByPeriod) {
  delete $UpdateByPeriod {$key};
 }
}

#tempolary for collecting
my @UpdateBySec = ();
my @UpdateByMin = ();
my @UpdateByHour = ();
my @UpdateByDay = ();
my @UpdateByWeek = ();
my @UpdateByMonth = ();

foreach my $obj (@Objects) {
#setting debug information
$obj -> debug ($Debugging);
#try create rrd file if not exists
if ( ! -e $obj ->{'RRD_FILENAME'} ) {
 printdebuginfo ("Create RRD file for ", $obj->name) if ($Debugging);
 $obj -> CreateRRD;
}

#grouping object to arrays by period
my $up = $obj-> UpdatePeriod ();
 if ($up =~ /^\d+$/) {
 #only seconds
 push (@UpdateBySec, [$obj, $up,0]); #object referenct, update period and last updated
 }else {
 #rest (minutes, hour, day... )
 $_ = $up;
 (my $value, my $per) = /(\d+)(\w+)/;

 push (@UpdateByMin, [$obj, $value,0]) if ($per eq 'm' );
 push (@UpdateByHour, [$obj, $value, 0]) if ($per eq 'h' );
 push (@UpdateByDay, [$obj, $value, 0]) if ($per eq 'd' );
 push (@UpdateByWeek, [$obj, $value, 0]) if ($per eq 'w' );
 push (@UpdateByMonth, [$obj, $value, 0]) if ($per eq 'M' );
 }
}
$UpdateByPeriod {'SEC'} = [ @UpdateBySec ] if ($#UpdateBySec >= 0);
$UpdateByPeriod {'MIN'} = [ @UpdateByMin ] if ($#UpdateByMin >=0 );
$UpdateByPeriod {'HOUR'} = [ @UpdateByHour ] if ($#UpdateByHour >= 0);
$UpdateByPeriod {'DAY'} = [ @UpdateByDay ] if ($#UpdateByDay >= 0);
$UpdateByPeriod {'WEEK'} = [ @UpdateByWeek ] if ($#UpdateByWeek >= 0);
$UpdateByPeriod {'MONTH'} = [ @UpdateByMonth ] if ($#UpdateByMonth >= 0);
if ($Debugging) { printObjectsList () };
}

sub printObjectsList {
 foreach my $key ( keys %UpdateByPeriod ) {
 my $tableref = $UpdateByPeriod {$key};
 print "-------$key--------\n";
 foreach my $key2 (@$tableref ) {
  my $obj = $$key2[0];
  print ($obj->name (),"  ",$obj->UpdatePeriod ()," \n");
 }
 }
}

#update obj by MIN, HOUR ...
sub UptadeSecondOBJ {
my $type =shift;
my $objtable;
my $obj;

  if ($Debugging > 1 ) {printdebuginfo ("Whole $type") }
  if (defined ($UpdateByPeriod {$type})) {
   #search for update
   $objtable = $UpdateByPeriod {$type};
   foreach my $key (@$objtable) {
   if (!$$key[2]) {
   #need update
   $obj = $$key[0];
   if ($Debugging) {
    printdebuginfo ("Update ",$obj->name ());
    }
   #update objects data
   $obj -> UpdateRRD ();
   $$key[2] = $$key[1]; #save last update time
   $$key[2] --;
    }
     else {$$key[2] -- }
   }
  }
}

sub printdebuginfo {
 my $t = localtime ();
 print $t," ";
 foreach my $info (@_){
 print $info;
 }
 print "\n";
}

#destroy object
sub  DestroyObj {
 my $objid = shift;
 my $OBJTab=shift;
 my $found=0;

 if ($Debugging) {
    printdebuginfo ("Attempt to destroing object $objid");
    }

 for (my $i=0;$i<@$OBJTab; $i++) {
  if ($$OBJTab[$i] -> name () eq $objid) {

    printdebuginfo ("Destroing object $objid") if ($Debugging) ;

    $found =1;
 #destroing
   my $objfile = $config {'OBJ_DIR' }.$objid;
   unlink ($objfile.'.obj'); #delete objects file
   unlink ($config {'RRD_DIR' }.$objid.'.rrd'); #delete his rrd file
   splice (@$OBJTab, $i, 1); #remove form  update list
   last;
  }
 }
 printdebuginfo ("Destroing object not found.") if ($Debugging && (! $found));

}

#testing if live object is osbserved yet
sub TestLive {
 my $obj = shift;
 my $Result = 0;
 my $filename = $obj->{'PNG_FILENAME'};
 $filename .= '_600.png';
 printdebuginfo ("Testing PNG file $filename")    if ($Debugging);
 if (-e $filename ) {
  my $mtime = stat ($filename) ->mtime;
  my $diff = time -$mtime;
  printdebuginfo ("Difference $diff second")    if ($Debugging);
  if ($diff < 1800) { # 30 min
  $Result=1
  }
 }
 if (!$Result) { #Destroy
   DestroyObj ($obj -> name (), \@LiveObjects);
  }
 return ($Result);
}

sub RecreateRRD {
 my $ObjName = shift;
 printdebuginfo ("Prepare recreate RRDfile for $ObjName")    if ($Debugging);
 for (my $i=0; $#Objects; $i++) {
  my $obj = $Objects[$i];
  if ($obj->name () eq $ObjName ){
    printdebuginfo ("Recreate RRDfile for $ObjName")    if ($Debugging);
    $obj = SafeLoad ($ObjName); # load new object to memory
    if (! defined ($obj)) { croak "Can't load object: $ObjName \n" };
    $obj -> debug ($Debugging);
    $obj -> CreateRRD; #create new rrd
    $Objects[$i] = $obj;

    #put again objects in period arrays
   CreateUpdateArray ();

    last;
  }
 }
}

#load object with security test
sub SafeLoad {
 my $objname =shift;
 my $obj = LoadObjectByName ($objname); # load object to memory
 if (defined ($obj)) {
  #check security
  if ( ! ($obj -> Check_Security ())) {
  my $msg="Insecure object: ".$obj ->name();
  croak ($msg);
  }
 } else
  {
   croak "Unknown object for load: $objname";
  }
 return ($obj)
}

#load all objects to memory and create rrd files
sub LoadAllObjects {

@Objects = ();
@LiveObjects = ();
#arrays objets to update
%UpdateByPeriod  = ();
LoadObjects (\@Objects); #load all objects in memory
#check security of all objects
foreach my $obj (@Objects) {
#setting debug information
 if ( ! ($obj -> Check_Security ())) {
  my $msg="Insecure object: ".$obj ->name();
  croak ($msg);
  }
}

printdebuginfo ("Objects loaded") if ($Debugging);

#put objects in period arrays
CreateUpdateArray ();

}

#delete all old PNG fils form stat dir

sub CleanPNGDir {

 printdebuginfo ("Cleaning PNG Dir") if ($Debugging);

my $PNGDir=$config{'STAT_PNG_DIR'};
if (opendir (DIR, "$PNGDir")) {
my @files = grep {/\.png$/} readdir DIR;
closedir DIR;

my $curtime = time;
foreach my $filename (@files) {
  my $mtime = stat ($PNGDir.$filename) ->mtime;
  my $diff = $curtime -$mtime;
  unlink ($PNGDir.$filename) if ($diff > 3600); #greater than 1 hour
}
} else { print "Cant open PNG dir $PNGDir: $! \n"; }
 

}
