On Tue, 13 May 2025 22:57:05 -0400 Ed Sabol <edwardjsa...@gmail.com> wrote:
Hi Ed, > > Well, the standard way of doing that is you fork a child process to run the > system command while the parent process loops and prints a period (or > whatever you prefer) every 1 second (make sure you call flush() after > printing!) while waiting for the child process to finish. Indeed. As it happens, the Paris Perl Mongueurs (http://paris.mongueurs.net/) held their monthly meeting last Wednesday night, and I requested advice from the fine members of the club. They rapidly concluded that's what I should do. I have a working prototype, it's modeled after https://stackoverflow.com/questions/471681/how-do-i-fork-properly-with-mod-perl2/. The page prints the asterisks while the fork builds the tar file, and adds the summary with file_size, interval, etc... when it detects the file. It probably needs some polishing, critics welcome. Excuse my French. https://pastebin.com/wn2Cw5KF (not sure what line 114 'warn "started\n" ;' is for, since all STDs are redirected to /dev/null?) >Lots of good examples of this can be found here: > > https://stackoverflow.com/questions/3193091/showing-progress-whilst-running-a-system-command-in-perl/ > Thank you for this. Interesting answer with waitpid, but have not tested yet > I also recommend that you still put an upper time limit on your subprocess > and not just loop infinitely. 30 minutes to an hour seems like a reasonable > choice. Depends on how big your tar files can be, of course. Gee, just when I thought I was done with this ;-) But you're right -- Bien à vous, Vincent Veyron https://legalcase.libremen.com Open source legal case, contract and insurance claims management software
package Marica::Base::Rapports::export_raw_data ; use utf8 ; use strict ; use POSIX 'setsid'; use Time::HiRes qw(gettimeofday tv_interval); use Apache2::Const -compile => qw(OK REDIRECT) ; sub handler { binmode(STDOUT, ":utf8") ; my $r = shift ; my $req = Apache2::Request->new($r) ; #récupérer les arguments my ( %args, @args ) ; @args = $req->param ; for (@args) { $args{$_} = Encode::decode_utf8( $req->param($_) ) ; #nix those sql injection/htmlcode attacks! $args{$_} =~ tr/<>;/-/ ; #les double-quotes viennent interférer avec le html $args{$_} =~ tr/"/'/ ; } my $id_client = $r->pnotes('session')->{id_client} ; my $content = '<h1 style="text-align: center;">' . _( 'Exportation des données', $r ) . '</h1>' ; #nom du répertoire de collecte des fichiers/données/documents dans /base/listing my $token_id = map +(0..9,"a".."z","A".."Z")[rand(10+26*2)], 1..32 ; my $recipient_dir = $r->document_root() . '/base/listing/' . $r->pnotes('session')->{_session_id} ; my $result_file_name = $r->pnotes('session')->{_session_id} ; my $final_file_name = '' ; #effacer les listings précédents de la session pour démarrer propre #il faut le faire avant de lancer le fork, sinon le test -e détecte le fichier précédent encore présent $ENV{'PATH'} = '/bin:/usr/bin' ; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; my @args = ( 'rm', '-r', $r->document_root() . '/base/listing/' . $r->pnotes('session')->{_session_id} ) ; system(@args) == 0 or warn "system @args failed: $?" ; my @args = ( 'rm', $r->document_root() . '/base/listing/' . $r->pnotes('session')->{_session_id} . '.tar.gz' ) ; system(@args) == 0 or warn "system @args failed: $?" ; if ( defined $args{go_for_it} ) { $ENV{'PATH'} = '/bin:/usr/bin' ; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; $r->content_type('text/html; charset=utf-8') ; $r->print('<!DOCTYPE html><html lang = "fr"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>' . $r->hostname . '</title></head><body><h3>Building tar file</h3><p>*') ; $r->rflush ; #clear the request buffer my $t0 = [gettimeofday]; #on sélectionne la base qui va bien my @databases = $r->dir_config->get('db_name') ; my $demo_user_name = $r->dir_config('demo_username') ; my $database = ( $r->pnotes('session')->{username} =~ /$demo_user_name/ ) ? $databases[1] : $databases[0] ; #on fait un fork pour lancer le long process #le fork crée le .tar.gz à télécharger $SIG{CHLD} = 'IGNORE'; defined (my $kid = fork) or die "Cannot fork: $!\n" ; if ($kid) { #print "Parent $$ has finished, kid's PID: $kid\n" ; } else { # chdir to '/' stops the process from preventing an unmount chdir '/' or die "Can't chdir to /: $!" ; open STDIN, '/dev/null' or die "Can't read /dev/null: $!" ; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!" ; open STDERR, '>/tmp/log' or die "Can't write to /tmp/log: $!" ; setsid or die "Can't start a new session: $!" ; my $oldfh = select STDERR ; local $| = 1 ; select $oldfh ; # warn "started\n" ; #créer les répertoires de stockage mkdir $recipient_dir ; mkdir '/tmp/marica_data' ; #dump du schéma de la base my @args = ('pg_dump', '-s', '-f', $recipient_dir . '/marica.out', $database) ; system(@args) == 0 or warn "system @args failed: $?" ; #la commande d'exportation des données du client ressemble à ça: #psql -f /home/lib/Marica/Base/Procedures/Reversibility/export_raw_data.sql -v id_client=13 -v database=marica postgres #on crée provisoirement les fichiers data dans /tmp/marica_data @args = ('psql', '-f', '/home/lib/Marica/Base/Procedures/Reversibility/export_raw_data.sql', '-v', 'id_client=' . $r->pnotes('session')->{id_client}, '-v', 'database=' . $database, 'postgres') ; system(@args) == 0 or warn "system @args failed: $!" ; #if faut les déplacer dans $recipient_dir @args = ('mv', '/tmp/marica_data', $recipient_dir . '/data') ; system(@args) == 0 or warn "system @args failed: $!" ; #copier le fichier create_db.sh et remplacer =id_client par la bonne valeur my $id_client = $r->pnotes('session')->{id_client} ; my $script ; open (my $fh_in, "<:encoding(UTF-8)", '/home/lib/Marica/Base/Procedures/Reversibility/create_db.sh') or die "Can't open create_db.sh : $!" ; while ( <$fh_in> ) { $script .= $_; } $script =~ s/=id_client/=$id_client/ ; close $fh_in ; #création du fichier pour le client my $out_file = $recipient_dir . '/create_db.sh' ; open (my $fh_out, ">:encoding(UTF-8)", $out_file) or warn "Can't open $out_file : $!" ; #ajouter le BOM pour que les tableurs s'ouvrent avec le bon encodage (utf8) #on peut aussi utiliser chr(65279); #MS-Office a besoin de ça pour identifier l'encodage print $fh_out chr(0xFEFF) ; print $fh_out $script ; close $fh_out ; #copier le fichier import_raw_data.sql @args = ('cp', '/home/lib/Marica/Base/Procedures/Reversibility/import_raw_data.sql', $recipient_dir) ; system(@args) == 0 or warn "system @args failed: $!" ; #création du fichier tar compressé my $temp_name = $result_file_name . '.tmp' ; @args = ( 'tar', '-czf', $r->document_root() . '/base/listing/' . $temp_name, $recipient_dir) ; system(@args) == 0 or warn "system @args failed: $!" ; #renommer proprement le fichier tar pour détection par la boucle do { } $final_file_name = $result_file_name . '.tar.gz' ; rename $r->document_root() . '/base/listing/' . $temp_name , $r->document_root() . '/base/listing/' . $final_file_name ; CORE::exit(0); # terminate the process } # if ($kid) #pendant que le fork travaille, on vérifie si le fichier .tar.gz final existe #tant qu'il n'est pas présent, on envoie un nouvel élément toutes les secondes my $i = 1 ; do { my $final_result = $r->document_root() . '/base/listing/' . $result_file_name . '.tar.gz' ; $i = 0 if -e $final_result ; $r->print('*') ; $r->rflush; #clear the request buffer sleep( 1 ) ; } while ( $i ) ; $r->print( '</p>' ) ; $r->rflush; #clear the request buffer my $t1 = [gettimeofday] ; my $t0_t1 = tv_interval $t0, $t1 ; my $file_size = -s $r->document_root() . '/base/listing/' . $result_file_name . '.tar.gz' ; $content = '<pre> using db : ' . $database . ' recipient_dir : ' . $recipient_dir . ' file size : ' . $file_size . ' interval : ' . $t0_t1 . ' </pre>' ; my $download_link = '<a href="/base/listing/' . $result_file_name . '.tar.gz">Télécharger ' . $result_file_name . '.tar.gz</a>' ; my $download_zone = ' <h3>Lien</h3> <p>' . $download_link . ' </p></body>' ; $content .= $download_zone ; $r->print( $content ) ; return Apache2::Const::OK ; } else { $content .= presentation($r) ; } # if ( defined $args{go_for_it} ) $r->content_type('text/html; charset=utf-8') ; $r->no_cache(1) ; print $content ; return Apache2::Const::OK ; } 1 ; sub presentation { my $r = shift ; my $content = '<h2>Instructions</h2>' ; $content .= '<p>Cette procédure crée un fichier contenant toutes les données stockées dans la base pour votre compte</p>' ; $content .= '<p>Ce fichier est une archive tar contenant :</p>' ; $content .= ' <ul> <li>le fichier create_db.sh qui crée la base de données Postgresql pour accueillir les données (nommée "import_raw_data")</li> <li>le fichier marica.out contenant le schema de la base de données</li> <li>le fichier import.sql qui importe les données</li> <li>le répertoire data qui contient les données (séparateur de données "TAB")</li> </ul>' ; $content .= '<h3>Utilisation</h3> <ul> <li>Créer le répertoire /tmp/marica</li> <li>Extraire de l\'archive tar les 4 éléments ci-dessus, et les placer dans /tmp/marica </li> <li>Rendre create_db.sh executable</li> <li>Executer create_db.sh en tant que super-utilisateur</li> </ul>' ; my $form = ' <form action=export_raw_data method=POST> <p><input type=submit value="Exporter les données"><input type=hidden name=go_for_it value=0></p> </form> ' ; $content .= $form ; return $content ; } #sub presentation