/usr/local/CPAN/oEdtk/oEdtk/trackEdtk.pm


package oEdtk::trackEdtk;

BEGIN {
		use oEdtk::Main	0.42;
		use Config::IniFiles;
		use Sys::Hostname;
		use Digest::MD5 	qw(md5_base64);
		use DBI;
		use Cwd			qw(abs_path);
		use strict;

		use Exporter;
		use vars 		qw($VERSION @ISA  @EXPORT_OK); # @EXPORT %EXPORT_TAGS);
	
		$VERSION		= 0.0034;
		@ISA			= qw(Exporter);
#		@EXPORT		= qw(
#						);

		@EXPORT_OK	= qw(
						ini_Edtk_Conf 		conf_To_Env 
						env_Var_Completion

						init_Tracking 		track_Obj
						define_Mod_Ed		define_Job_Evt
						define_Track_Key 

						edit_Track_Table
						create_Track_Table	prepare_Tracking_Env
						drop_Track_Table
						)

	}

	# 3 méthodes possibles d'alimentation (config edtk.ini -> EDTK_TRACK_MODE) :
	# -1- DB  : suivi directement dans un SGBD (DB)-> ralentissement du traitement de prod (insérer les info de suivi en fin de traitement pour limiter l'impact => END du module ?)
	# -2- FDB : suivi via SQLite -> pas de gestion de plusieurs accès en temps réel => créer 1 fichier db par process (procDB)-> organiser une consolidation des données 
	# -3- LOG : fichiers de suivi à plat -> organiser une consolidation des données
	#
	# ? A VOIR : bug dans la création dynamique du fichier SQLite, on utilise pas le TSTAMP/PROCESS_ID ???

	my $DBI_DNS	="";
	my $DBI_USER	="";
	my $DBI_PASS	="";
	my $TABLENAME	="tracking_oEdtk";

	my $ED_HOST	="";
	my $ED_TSTAMP	="";
	my $ED_PROC	="";
	my $ED_SNGL_ID	="";
	my $ED_USER	="";
	my $ED_SEQ	="";
	my $ED_APP	="";
	my $ED_MOD_ED	="";
	my $ED_JOB_EVT	="";
	my $ED_OBJS	="";
	my @ED_K_NAME;
	my @ED_K_VAL;

	my @TRACKED_OBJ;
	my @DB_USER_COL;

	my $NOK=-1;


sub ini_Edtk_Conf {
	# recherche du fichier de configuration
	# renvoi le chemin au fichier de configuration valide
	my $iniEdtk 	=$INC{'oEdtk/trackEdtk.pm'};
	$iniEdtk		=~s/(trackEdtk\.pm)//;
	$iniEdtk 		.="iniEdtk/edtk.ini";
	my $hostname	=uc ( hostname());

	# OUVERTURE DU FICHIER DE CONFIGURATION
	my $tmpIniEdtk	=$iniEdtk;
	my $confIni;
	while ($tmpIniEdtk ne 'local'){
		if (! (-e $tmpIniEdtk)){die "ERR. config file not found : $tmpIniEdtk\n";}
			$confIni	= Config::IniFiles->new( -file => $tmpIniEdtk, -default => 'DEFAULT');

			$iniEdtk	=$tmpIniEdtk;
			# recherche de la variable iniEdtk dans la section '$hostname' ou par défaut
			#  dans la section 'DEFAULT' (cf méthode new)
			$tmpIniEdtk=$confIni->val( $hostname, 'iniEdtk' );

		# si iniEdtk == fichier courant alors mettre la valeur à local (éviter les boucle infinies)
		if ($tmpIniEdtk eq $iniEdtk) { last; }
	}

	$ENV{EDTK_INIEDTK}	=$iniEdtk;		
return $iniEdtk;
}


sub conf_To_Env ($;$) {
	# charge les sections demandées du fic de config dans la configuration d'environnement
	# en param, passer le chemin d'accès au fichier ini + la section à charger
	# si la section HOSTNAME existe elle surcharge les valeurs de la section
	my $confIni=shift;
	my $section=shift;
	$section ||='DEFAULT';
	
	if (-e $confIni){
	} else {
		die "ERR. config file not found : $confIni\n";
	}

	my $hostname	=uc ( hostname());

	# OUVERTURE DU FICHIER DE CONFIGURATION
	my %hConfIni;
	tie %hConfIni, 'Config::IniFiles',( -file => $confIni );

	# CHARGEMENT DES VALEURS DE LA SECTION
	my %hSection;
 	if (exists $hConfIni{$section}) {
		%hSection =%{$hConfIni{$section}};
	}

	# CHARGEMENT EN SURCHARGE DES VALEURS PROPRES AU HOSTNAME
	my %hHostname;
	if (exists $hConfIni{$hostname}) {
		undef %hSpecific;
		%hHostname =%{$hConfIni{$hostname}};
	} else {
		warn "INFO machine '$hostname' inconnue dans la configuration";
	}
 	%hConfig=(%hSection,%hHostname);
 
	my $self = abs_path($0);
 	$self =~ /([\w\.\-]+)[\/\\]\w+\.\w+$/;
	# DÉFINITION POUR L'ENVIRONNEMENT DE DÉV DE L'APPLICATION/PROGRAMME COURANT
	$hConfig{'EDTK_PRGNAME'} =$1;
	#$hConfig{'EDTK_OPTJOB'}	=$EDTK_OPTJOB;

	# mise en place des variables d'environnement
	while ((my $cle, my $valeur) = each (%hConfig)){
		$valeur ||="";
		$ENV{$cle}=$valeur;
	}
1;
}


sub env_Var_Completion (\$){
	# développe les chemins en remplaçant les variables d'environnement par les valeurs réelles
	# tous les niveaux d'imbrication définis dans les variables d'environnement sont développés
	# nécessite au préalable que les variables d'environnements soient définies
	my $rScript =shift;
	# il peut y avoir des variables dans les variables d'environnement elles mêmes
	while (${$rScript}=~/\$/g) {
		${$rScript}=~s/\$(\w+)/${ENV{$1}}/g;
	}
	${$rScript}=~s/(\/)/\\/g;
1;
}


################################################################################
# PARTIE DEFINITION SUIVI DE PRODUCTION
#
#
	my $DBH;
	my %h_subInsert;
	
	# definition de la méthode d'insertion
	$h_subInsert{'LOG'}=\&subInsert_Log;
	$h_subInsert{'DB'} =\&subInsert_DB;
	$h_subInsert{'FDB'}=\&subInsert_DB;
	$h_subInsert{'none'}=\&noSub;

	my %h_subClose;
	$h_subClose{'DB'} =\&subClose_DB;
	$h_subClose{'FDB'}=\&subClose_DB;


sub prepare_Tracking_Env() {
	my $iniEdtk	=ini_Edtk_Conf();
	conf_To_Env($iniEdtk, 'ENVDESC');
	conf_To_Env($iniEdtk, 'EDTK_DB');
	maj_sans_accents($ENV{EDTK_TRACK_MODE});

1;
}

sub open_Tracking_Env(){
	if ($ENV{EDTK_TRACK_MODE} =~/FDB/i){
		# DB FILE NOWTIME/PROCESS
		$ENV{EDTK_DBI_DNS}=~s/(.+)\.(\w+)$/$1\.$ED_TSTAMP\.$ED_PROC\.$2/;
		warn "INFO tracking to $ENV{EDTK_DBI_DSN}\n";
		create_Track_Table($ENV{EDTK_DBI_DSN});
		open_DBI();
			
	} elsif ($ENV{EDTK_TRACK_MODE} =~/LOG/i){
		# log

	} elsif ($ENV{EDTK_TRACK_MODE} =~/DB/i){
		# DB connexion tracking
		open_DBI();

	} else {
		$ENV{EDTK_TRACK_MODE} = "none";
		
	}

	if (!($h_subInsert{$ENV{EDTK_TRACK_MODE}}) && !($h_subCreate{$ENV{EDTK_TRACK_MODE}})){
		warn "INFO $ENV{EDTK_TRACK_MODE} undefined - tracking halted\n";
		$ENV{EDTK_TRACK_MODE} ="none";
	}

1;
}

sub open_DBI(){
	my $dbargs = {	AutoCommit => 	$ENV{EDTK_DBI_AutoCommit},
			RaiseError => 	$ENV{EDTK_DBI_RaiseError},
			PrintError => 	$ENV{EDTK_DBI_PrintError}};

	$DBH = DBI->connect(		$ENV{EDTK_DBI_DSN},
					$ENV{EDTK_DBI_DSN_USER},
					$ENV{EDTK_DBI_DSN_PASS}
			#		,$dbargs
				)
			or die "ERR no connexion to $ENV{EDTK_DBI_DSN} " . DBI->errstr;

1;
}


sub init_Tracking(;@){
	my $Mod_Ed	=shift;
	my $Typ_Job	=shift;
	my $Job_User	=shift;
	my @Track_Key	=@_;
	define_Mod_Ed	($Mod_Ed);	# U(ndef) by default 
	define_Job_Evt ($Typ_Job);	# S(pool) by default
	define_Job_User($Job_User);	# user job request, by default 'None'
	$ED_HOST	=hostname();
	$ED_TSTAMP	=nowTime();
	$ED_PROC	=$$;
	$ED_SEQ		=0;			# (dynamic, private)
	$ED_SNGL_ID	= md5_base64($ED_HOST.$ED_TSTAMP.$ED_PROC);

	&prepare_Tracking_Env();
	&open_Tracking_Env();
	
	my $indice =0;
	foreach my $element (@Track_Key) {
		define_Track_Key($element, $indice++);	# default key for indiced col_name
	}

	$0 =~/([\w-]+)[\.plmex]*$/;
	$1 ? $ED_APP ="application" : $ED_APP =$1;

	$ED_OBJS		=1;		## default insert unit count (dynamic)

	warn "INFO tracking init ( track mode : $ENV{EDTK_TRACK_MODE}, edition mode : $ED_MOD_ED, job type : $ED_JOB_EVT, user : $ED_USER, optional Keys : @ED_K_NAME )\n";

return $ED_SNGL_ID;
}


sub track_Obj (;@){
	# track_Obj ([$ED_OBJS, $ED_JOB_EVT, @ED_K_VAL])
	#  $ED_OBJS (optionel) : nombre d'unité de l'objet (1 par defaut)
	#  $ED_JOB_EVT (optio) : evenement en question (cf define_Job_Evt)
	#  @ED_K_VAL(optionel) : valeurs des clefs optionnels définies avec init_Tracking (même ordre)

	$ED_SEQ++;
	$ED_OBJS 		=shift;
	$ED_OBJS		||=1;
	define_Job_Evt (shift);
	@ED_K_VAL =@_;

	undef @TRACKED_OBJ;
	push (@TRACKED_OBJ, nowTime());
	push (@TRACKED_OBJ, $ED_USER);
	push (@TRACKED_OBJ, $ED_SEQ);
	push (@TRACKED_OBJ, $ED_SNGL_ID);
	push (@TRACKED_OBJ, $ED_APP);
	push (@TRACKED_OBJ, $ED_MOD_ED);

	push (@TRACKED_OBJ, $ED_JOB_EVT);
	push (@TRACKED_OBJ, $ED_OBJS);
	undef @DB_USER_COL;
	for (my $i=0 ; $i <= $#ED_K_VAL ; $i++) {
		push (@TRACKED_OBJ, $ED_K_NAME[$i]	|| "");
		push (@TRACKED_OBJ, $ED_K_VAL[$i]	|| "");
		push (@DB_USER_COL, "ED_K${i}_NAME");
		push (@DB_USER_COL, "ED_K${i}_VAL");
	}
	
	&{$h_subInsert{$ENV{EDTK_TRACK_MODE}}}
		or die "ERR. undefined EDTK_TRACK_MODE -> $ENV{EDTK_TRACK_MODE}\n";
1;
}


sub define_Mod_Ed ($) {
	# Printing Mode : looking for one of the following :
	#	 Undef (default), Batch, Tp, Web, Mail
	my $value	 =shift;

	if ($value) { $ED_MOD_ED =$value }; 
	$ED_MOD_ED	=~ /([NBTWM])/;
	$ED_MOD_ED	=$1;
	$ED_MOD_ED	||="U"; 	# Undef by default

return $ED_MOD_ED;
}


sub define_Job_Evt ($) {
	# Job Event : looking for one of the following : 
	#	 Job (default), Spool, Document, Line, Warning, Error
	my $value	 =shift;

	if ($value) { $ED_JOB_EVT =$value };
	$ED_JOB_EVT	=~ /([JSDLWE])/;
	$ED_JOB_EVT	=$1;
	$ED_JOB_EVT	||="J"; 	# Job by default

return $ED_JOB_EVT;
}


sub define_Job_User ($) {
	# USER JOB REQUEST : LOOKING FOR ONE OF THE FOLLOWING :
	#	 None (default), user Id (max 10 alphanumerics)
	my $value	 =shift;

	if ($value=~/(\w{1,10})/) {
		$ED_USER	=$1;	
	} else {
		$ED_USER	="None";
	}

return $ED_USER;
}


sub define_Track_Key ($;$) {
	# TO DEFINE THE COL_NAME OF THE N INDICED TRACKING KEY
	my $value	 =shift;
	my $indice =shift;
	$indice 	||=0;

	if (!defined $ENV{EDTK_MAX_USER_KEY}) {	
		warn "WARN : tracking key undefined\n";
		return 0;

	} elsif ($indice gt ($ENV{EDTK_MAX_USER_KEY}-1)) { 
		warn "WARN : tracking key not allowed (limit is $ENV{EDTK_MAX_USER_KEY})\n";
		return 0;

	} elsif (length ($value) > 5) {
		$value=~s/^(\w{5})(.*)/$1/;
		warn "WARN : redefined col as '$value'";
	}
	if ($value) { $ED_K_NAME[$indice] =$value; }

	$ED_K_NAME[$indice] =~ s/\s/\_/g;
	maj_sans_accents($ED_K_NAME[$indice]);

return $ED_K_NAME[$indice];
}


sub subInsert_Log(){
	# DANS LE CAS D'UN SUIVI SOUS FORME DE FICHIERS LOG
	# à compléter avec l'utisation du remplaçant du Logger

	my $request	=join (", ", @TRACKED_OBJ);
	warn "$request\n";

1;
}


sub subInsert_DB() {
	# constructuction de la commande SQL pour insertion dans une base DBI (file/DB)

	my $request="insert into $ENV{EDTK_DBI_TRACKING}"; 
	$request	.=" (";
	$request	.="ED_TSTAMP, ED_USER, ED_SEQ, ED_SNGL_ID, ED_APP, ED_MOD_ED, ED_JOB_EVT, ED_OBJ_COUNT";
	if (@DB_USER_COL) {
		$request	.=", ";
		$request	.=join (", ", @DB_USER_COL);	
	}
	$request	.=" ) values ('";
#	FORMATAGE DE LA DATE POUR LES SGBD 
#	$request	.=sprintf ("to_date('%014.f', 'YYYYMMDDHH24MISS'), '", shift @TRACKED_OBJ);
	$request	.=join ("', '", @TRACKED_OBJ);
	$request	.="')";

	$DBH->do($request);
	if ($DBH->err()) {
		warn "INFO ".$DBI::errstr."\n";
	}	

#	$DBH->commit();	# nécessaire si AutoCommit  vaut 0
#	$DBH->disconnect();
#	if ($DBH->err()) { warn "$DBI::errstr\n"; }
1;
}

sub noSub(){
	# FONCTION A VIDE POUR LES POINTEURS DE FONCTION %H_SUBINSERT
	# pour éviter d'utiliser des tests dans des fonctions répétitives
	# (faux switch/case)
return 1;
}


sub test_exist_table(){
	my $dbargs = {	AutoCommit => $ENV{EDTK_DBI_AutoCommit},
				RaiseError => $ENV{EDTK_DBI_RaiseError},
				PrintError => $ENV{EDTK_DBI_PrintError}};
	$DBH = DBI->connect($ENV{EDTK_DBI_DSN},
					$ENV{EDTK_DBI_DSN_USER},
					$ENV{EDTK_DBI_DSN_PASS}
					,$dbargs
				)
			or die "ERR no connexion to $ENV{EDTK_DBI_DSN} " . DBI->errstr;

	my $request="select * from $ENV{EDTK_DBI_TABLENAME}";

	$DBH->do($request);
	if ($DBI::errstr) {
		if ( $DBI::errstr =~/no such table/ ) { 
			$DBH->disconnect();
			return 0;
		}
		warn "INFO ".$DBI::errstr."\n";
		$DBH->disconnect();
		return $NOK; 
	}	
	$DBH->disconnect();

1;
}


sub edit_Track_Table(;$){
	my $request=shift;

	&open_DBI();
	
	my $ref_Tab =&fetchall_DBI($request);
	&edit_All_rTab($ref_Tab);
1;
}


sub create_Track_Table(){
	#my $dbi_dns=shift;

	# CREATE TABLE tablename [IF NOT EXISTS][TEMPORARY] (column1data_type, column2data_type, column3data_type);
	#&prepare_Tracking_Env();
	#$dbi_dns ||=$ENV{EDTK_DBI_DNS};
	
	my $dbargs = {	AutoCommit => 0,
				RaiseError => 0,
				PrintError => 0 };
	$DBH = DBI->connect($ENV{EDTK_DBI_DSN},
					$ENV{EDTK_DBI_DSN_USER},
					$ENV{EDTK_DBI_DSN_PASS},
					$dbargs)
			or die "ERR no connexion to $ENV{EDTK_DBI_DSN} " . DBI->errstr;

	my $struct="CREATE TABLE $ENV{EDTK_DBI_TABLENAME} ";
	$struct .="( ED_TSTAMP NUMBER(14)  NOT NULL";	# interesting for formated date and interval search
#	$struct .="( ED_TSTAMP VARCHAR2(14)  NOT NULL";	# most used
#	$struct .="( ED_TSTAMP DATE  NOT NULL";			# Not compatible
#	$struct .=", ED_HOST VARCHAR2(15) NOT NULL";		# hostname
#	$struct .=", ED_PROC VARCHAR2(6) NOT NULL";		# processus
	$struct .=", ED_USER VARCHAR2(10) NOT NULL";		# job request user 
	$struct .=", ED_SEQ NUMBER(9) NOT NULL";		# sequence
	$struct .=", ED_SNGL_ID VARCHAR2(22) NOT NULL";	# Single ID
	$struct .=", ED_APP VARCHAR2(15) NOT NULL";		# application name
	$struct .=", ED_MOD_ED CHAR";					# mode d'edition (Batch, Tp, Web, Mail)
	$struct .=", ED_JOB_EVT CHAR";				# niveau de l'événement dans le job(Spool, Document, Line, Warning, Error)
	$struct .=", ED_OBJ_COUNT NUMBER(15)";			# nombre d'éléments/objets attachés à l'événement

	for (my $i=0 ; $i lt $ENV{EDTK_MAX_USER_KEY} ; $i++) {
		$struct .=", ED_K${i}_NAME VARCHAR2(5)";	# nom de clef $i
		$struct .=", ED_K${i}_VAL VARCHAR2(30)";	# valeur clef $i
	}
	$struct .=")"; #, CONSTRAINT pk_$ENV{EDTK_DBI_TABLENAME} PRIMARY KEY (ED_TSTAMP, ED_PROC, ED_SEQ)";

	$DBH->do($struct);
	if ($DBI::errstr) {
		warn "INFO ".$DBI::errstr."\n";
	}	

	# my $seq ="CREATE SEQUENCE sq_$TABLENAME 
	#			MINVALUE 1
	#			MAXVALUE 999999999
	#			START WITH 1
	#			INCREMENT BY 1;";
	#$dbh->do("$seq");

	$DBH->commit();	# nécessaire si AutoCommit  vaut 0
	$DBH->disconnect();
1;
}


sub drop_Track_Table(){
	&prepare_Tracking_Env();
	&open_DBI();
	
	warn "=> Drop table $ENV{EDTK_DBI_TABLENAME} from $ENV{EDTK_DBI_DSN}, if exist\n\n";
	$DBH->do("DROP TABLE $ENV{EDTK_DBI_TABLENAME}");
	$DBH->disconnect;

1;
}


sub fetchall_DBI(;$) {
	# CONNEXION À UNE TABLE DBI POUR SELECT VERS UNE RÉFÉRENCE DE TABLEAU
	# sélection de toutes les données correspondant à un critère
	# option : requete à passer, exemple "SELECT * FROM TRACKING_OEDTK WHERE ED_MOD_ED = 'T'"
	#		par defaut vaut 'SELECT * from $ENV{EDTK_DBI_TABLENAME}' 
	my $request =shift;
	$request ||="SELECT * from $ENV{EDTK_DBI_TABLENAME}";
	
	my $sql = qq($request); 
	my $sth = $DBH->prepare( $sql );
	$sth->execute () 
			|| warn "ERR. DBI exec " . $DBH->errstr ; 
	
	my $rTab = $sth->fetchall_arrayref;
	
	$sth->{Active} = 1;	# resolution du bug SQLite "closing dbh with active statement" http://rt.cpan.org/Public/Bug/Display.html?id=9643
	$sth->finish();
	#$DBH->commit();	# nécessaire si AutoCommit  vaut 0 ???
	if ($DBI::errstr) {
		warn $DBI::errstr."\n";
	}	

return $rTab;
}


sub edit_All_rTab($){
	# EDITION DE L'ENSEMBLE DES DONNÉES D'UN TABLEAU PASSÉ EN REFÉRENCE
	#  affichage du tableau en colonnes 
	my $rTab=shift;

	for (my $i=0 ; $i<=$#{$rTab} ; $i++) {
		my $cols = $#{$$rTab[$i]};
		print "\n$i:\t";
			
		for (my $j=0 ;$j<=$cols ; $j++){
			print "$$rTab[$i][$j]" if (defined $$rTab[$i][$j]);
		}
	}
	print "\n";

1;
}


sub subClose_DB(){
	$DBH->commit() if ($ENV{EDTK_DBI_AutoCommit} eq 0 );	# nécessaire si AutoCommit  vaut 0
#	$DBH->disconnect();
1;
}

END {
	if (exists $h_subClose{EDTK_TRACK_MODE}) {
		&{$h_subClose{$ENV{EDTK_TRACK_MODE}}} ;
	}
}
1;




# NOTES 
#
# LISTE DES TABLES
# select table_name from tabs;
#
# Lister les tables du schéma de l'utilisateur courant :
# SELECT table_name FROM user_tables;
#
# Lister les tables accessibles par l'utilisateur :
# SELECT table_name FROM all_tables;
#
# Lister toutes les tables (il faut être ADMIN) :
# SELECT table_name FROM dba_tables; 
#
# DESCRIPTION DE LA TABLE :
# desc matable; 	# retourne les champs et leurs types 



# EXEMPLES REQUETES - http://fadace.developpez.com/sgbdcmp/fonctions/
#
# SELECT * FROM TRACKING_OEDTK WHERE ED_JOB_EVT='S';
# SELECT * FROM TRACKING_OEDTK WHERE ED_MOD_ED='T';
# SELECT SUM(ED_OBJ_COUNT) AS "OBJETS" FROM TRACKING_OEDTK WHERE ED_JOB_EVT='D';
# SELECT COUNT(ED_OBJ_COUNT) AS "OBJETS" FROM TRACKING_OEDTK WHERE ED_JOB_EVT='D';
# SELECT DISTINCT ED_SNGL_ID FROM TRACKING_OEDTK;
# SELECT COUNT (DISTINCT ED_SNGL_ID) FROM TRACKING_OEDTK ;
# SELECT COUNT (DISTINCT ED_SNGL_ID) FROM TRACKING_OEDTK WHERE ED_JOB_EVT='D';
# SELECT COUNT (DISTINCT ED_SNGL_ID) AS "TOTAL" FROM TRACKING_OEDTK  WHERE ED_JOB_EVT='D' AND ED_MOD_ED='T';
# SELECT ED_TSTAMP, ED_APP, ED_SNGL_ID FROM TRACKING_OEDTK WHERE ED_MOD_ED='T' AND ED_JOB_EVT='S';
# SELECT  to_char(ED_TSTAMP, 'DD/MM/YYYY HH24:MM:SS'), ED_APP, ED_SNGL_ID FROM TRACKING_OEDTK WHERE ED_MOD_ED='T' AND ED_JOB_EVT='S';
# SELECT  to_char(ED_TSTAMP, 'DD/MM/YYYY HH24:MM:SS') AS TIME , ED_APP, ED_SNGL_ID FROM TRACKING_OEDTK WHERE ED_MOD_ED='B' AND ED_JOB_EVT='S';

# update EDTK_FILIERES SET ed_postcomp =2020  where ed_postcomp<>'FilR100';

#
# END