# # Copyright (c) 2004-2007 - Consultas, PKG.fr # # This file is part of A2P. # # A2P is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # A2P is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with A2P; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # $Id$ # package AFPDS::afpds2tex; use strict ; use Encode 'from_to' ; use Time::HiRes qw( gettimeofday tv_interval usleep ); use File::stat ; use A2P::Globals ; use A2P::Syslog ; use A2P::Common ; use A2P::Com qw( GetCom comINF comREQ comFILE ); use A2P::Tools qw( debugbuffer compute mychomp myreadline ) ; use AFPDS::Flux ; use AFPDS::ControlRecord ; use A2P::XML ; use A2P::JobStatus 'a2pjobstate' ; BEGIN { our $VERSION = sprintf "%s", q$Rev: 1023 $ =~ /([0-9.]+)\s+/ ; } our $VERSION ; # Files handles & generation flag my ( $TexFile, $ArchFile, $AfpFile, $LCKNAME, $AFPDSFILE ); my $GenFlag = 0 ; my $ErrFlag = 0 ; my $AfpName = ""; my $Job = ""; my $self = 0 ; my $file = 0 ; my $mtime = 0 ; # @skip will be used to continue an afp job when an error occurs on a sub job # It should be reset to () at each new job my @skip = () ; my $RateTimer = [] ; my $afpbuffer = "" ; my $min_conv_time = 0 ; sub _job_tag { return $Job ? "$Job".($file?"-$file":"").": " : "" } # Function to open TeX file and his shell environment file for writing sub OpenFiles { my $filename = $_[1] ; open($TexFile, ">$filename.tex") or &Abort(107, "$filename.tex", "$!"), return 0; $LCKNAME = "$filename.tex.LCK" ; open(LCK, ">$LCKNAME") or &Abort(107, $LCKNAME, "$!"), return 0; open($ArchFile, ">$filename.arch") or &Abort(107, "$filename.arch", "$!"), return 0; return 1 unless ( $PARALLELE_VALIDATION or $KEEP_AFPJOB ); open($AfpFile, ">$filename.afp") or &Abort(107, "$filename.afp" , "$!"), return 0; return 1 ; } # Function to close TeX file and his shell environment file sub CloseFiles { close($TexFile) if (defined($TexFile)); close($ArchFile) if (defined($ArchFile)); close($AfpFile) if (defined($AfpFile)); if (defined($LCKNAME) and -e $LCKNAME) { close(LCK); unlink $LCKNAME ; } $GenFlag = 0 ; } # Arguments: # 1 - The texname to process # 2 - AFPDS file to process sub ConvertFile { $self = shift ; my $texbase = shift ; my $afpdsfile = shift ; $Job = shift ; # Current job name for queueing announcement my $startpos = shift || 0 ; # The start position in AFP file for splitting # Forget any previous status $ErrFlag = 0 ; @skip = () ; # Same computation as in SpoolManager module ( $AfpName ) = $afpdsfile =~ $AFPNAME_REGEX ; &Debug("Afpname set to '$AfpName' from Texbase '$afpdsfile'"); # First job state update in spool &a2pjobstate( $Job , 2 , '.', { JOBID => $Job } ) or &Info("Can't initialize job status at step 2 with JOBID $Job"); $afpdsfile = $AFPSPOOL . '/' . $afpdsfile unless ( -e $afpdsfile ); # Provided texbase must not be an existing folder return &Abort( 119, $texbase) if ( -d $texbase ); # Create texbase as folder mkdir $texbase , 0775 or return &Abort( 106, $texbase, "$!"); # Here, texbase will be a folder to delete, so inform JobManager of it $self->Return( $Job , $texbase ); # Opening files for reading open($AFPDSFILE, "<", "$afpdsfile") or return &Abort( 107, $afpdsfile, "$!"); # Read AFPDS files in binary mode binmode($AFPDSFILE) or return &Abort( 108, $afpdsfile, "$!"); &Debug("Starting AFPDS conversion of $afpdsfile to $texbase folder"); ############################################################################ # Check if records are terminated by CR-LF: # search for pattern 0x0D0A in the last 10 chars of afpds file ############################################################################ my ( $readmode , $buffer , $EOF , $inXML ) = ( 0 , "" , 0 , 0 ) ; { my ( $EOL , $offset ) = ( 0 , 9 ); # 1. Check file contents greater than 7 chars, or this is probably # a corrupted file seek $AFPDSFILE , 0 , 2 ; return &Abort(124, $afpdsfile) if ( tell($AFPDSFILE) <= 7 ); # 2. Load the last 10 chars in the buffer seek $AFPDSFILE , -10 , 2 ; return &Abort(109, $afpdsfile, "$!") if ( read($AFPDSFILE , $buffer , 10) != 10); # 3. Scan loaded buffer for 0x0D0A string starting from offset 8 # in the buffer while ( $offset-- > 0 ) { $EOL = &compute( \$buffer , $offset , 2 ); $readmode += 2 , last if ( $EOL == 0x0D0A ); } &Debug(sprintf( "Readmode is $readmode as found CRLF=0x%04x at offset %d", $EOL,$offset)); # Keep End of File information $EOF = tell($AFPDSFILE); } # Get file time creation my $stat = stat($afpdsfile); $mtime = $stat->mtime() ; ############################################################################ # Rewind file to startpos (beginning by default) seek $AFPDSFILE , $startpos , 0 ; ############################################################################ # # Read the AFPDS file # ############################################################################ my ( $long , $char , $cctrl , $tmp , $maxrepeat ); $file = 1 ; $afpbuffer = "" ; # Timer to get a chrono on the current conversion $RateTimer = [ &gettimeofday() ] ; # Set minimum conversion time (used for big jobs to limit convertion rate) $min_conv_time = $USLEEP ; # MAX_CONV_RATE is a maximum sub-job number to generate by seconds $min_conv_time = 1_000_000 / $MAX_CONV_RATE if ($MAX_CONV_RATE); # Set chrono to return progression status when required my @StartTime = &gettimeofday(); my $tv_timing = $CONV_STATUS_TIMING ; $buffer = getc( $AFPDSFILE ) ; while ( defined( $buffer ) and ! eof($AFPDSFILE) ) { # Update afpbuffer $afpbuffer .= $buffer ; # Manage the 0x5A char if ( ord( $buffer ) == 0x5A and ! $inXML ) { # Read the field length read $AFPDSFILE , $buffer , 8 ; $long = &compute( \$buffer , 0 , 2 ) ; # Update afpbuffer $afpbuffer .= $buffer ; # Keep preamble for debugging my $preamble = $buffer ; # Abort if field is too short &Abort(114,$long) if ( $long < 8 ); # Compute other attributes # cf doc IBM AFP - programming guide and line data ref - 54438842 my $ident = &compute( \$buffer , 2 , 3 ) ; my $flag = &compute( \$buffer , 5 , 1 ) ; my $reserved = &compute( \$buffer , 6 , 2 ) ; # In case file is line delimited, then $readmode = 2, so we # need to check and found 2 more chars to be CRLF $long -= 8 - $readmode ; &Debug(sprintf("Long is %d, Ident is 0x%x and Flag is 0x%x", $long, $ident, $flag )); if ( $long > 0 ) { # Read the value of $ident read $AFPDSFILE , $buffer , $long ; # Update afpbuffer $afpbuffer .= $buffer ; # Some lines are full filled with spaces... if ( $readmode > 0 ) { # The value should end with 0x0D0A, # else read until end of line... $tmp = &compute( \$buffer , -2 , 2 ); if ( $tmp != 0x0D0A ) { # Read till the real end of line my $plus = &myreadline( \*$AFPDSFILE , \$buffer ); # Update afpbuffer $afpbuffer .= substr($buffer,-$plus) if $plus ; $long += $plus ; # Definitive check $tmp = &compute( \$buffer , -2 , 2 ); if ( $tmp != 0x0D0A ) { &debugbuffer( $preamble . $buffer ); &Abort(110,$tmp); } } $long -= &mychomp( \$buffer, 'keep spaces' ) ; } # Check we read as byte as we need if ( length($buffer) != $long ) { &debugbuffer($preamble . $buffer); &Abort(111,length($buffer),$long); } } else { # Reset the buffer if nothing has been needed to read $buffer = "" ; } if (defined($self->{AFPDS})) { &debugbuffer( $preamble . $buffer ); my @params = ( $ident, $flag, $reserved, $long, $buffer, $texbase ); # Don't try to analyse 5A record if @skip is not empty &Abort( 200, $self->{AFPDS}->geterror() ) if ( ! @skip and $self->{AFPDS}->got5Achar(@params) < 0 ); } else { # Rise abort if we got 0x5A AFP record without AFPDS Flux # Object initialization &Abort(3); } # Check got5Achar didn't rise an error &Abort( 118, $ident, $flag, $reserved, $long, join('',map { sprintf("%X",$_) } split(//,$buffer) )) unless (defined($self->{AFPDS})); ######################################################################## ## Manage the 0x7B char '#' for C930 control record (or RC) ## ######################################################################## } elsif ( ord( $buffer ) == 0x7B or $inXML ) { my ( $begin , $Type ) = ( $buffer , "" ); read( $AFPDSFILE , $Type , 3 ) ; # And check this is really a numbered RC read( $AFPDSFILE , $buffer , 1 ) ; # Update begin to later update afpbuffer my $rawtype = $Type . $buffer ; # Must found another '#' as format validation if not in XML reading &Abort(112,$Type) unless ( $inXML or ord( $buffer ) == 0x7B ); &from_to( $Type , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); my $afpds = $self->{AFPDS} ; # Generate a print if still after the first print analysis if ( ! $inXML and $GenFlag and ( $Type eq '001' or $Type eq '100' )) { if (defined($afpds)) { &Debug("File#${file}: Start TeX file generation"); # Output this TeX file or rise an ABTERM unless ( @skip or &generateTeX ) { &Abort(103, $file, defined($self->{RC}->{$Type}) ? $self->{RC}->{$Type}->getoutfile() : "with undefined '$Type' RC" ); } # Increment file index $file ++ ; } else { # Abort if AFPDS object has vanished &Abort(4) ; } } # Initialise a new AFPDS flux only on record Type 001 or 100 if (! $inXML and ( $Type eq '001' or $Type eq '100' )) { &UPSTAT('GETNEWJOB'); # Reset skipping array to try another document if (@skip) { # Starting new job after ABTERM &Abort(500,$file); # Warn we are trying to start a new job @skip = () ; } # Destroy last afpds object if needed and create new one delete $self->{AFPDS} if (defined($afpds)); undef $afpds ; &Debug("Initializing AFPDS objects"); $afpds = new AFPDS::Flux( $afpdsfile , $self , $Job ); # And don't forget to keep a copy $self->{AFPDS} = $afpds ; } # Update job index in flux in case it has changed $afpds->setjobindex( $file ); # Still check AFPDS Flux object exists &Abort(117,$Type) unless (defined($afpds)); my $RC ; my ( $tA , $tB ) = ( ord($begin) == 0x7B , ord($buffer) == 0x7B ) ; # Continue last not sized RC unless it is the stop one if ( $inXML and (( $inXML eq '200' and ! ( $Type eq '201' and $tA and $tB )) or ( $inXML eq '201' and ! ( $Type eq '202' and $tA and $tB )))) { &Debug("In XML $inXML: Type would be '" . ( $tA ? '#' : '.' ) . $Type . ( $tB ? '#' : '.' ) . "'"); # Still reading an XML, seek to beginning of line to avoid # missing an EOL if the begin string seek $AFPDSFILE, -5 , 1 ; # Type has been saved in inXML $Type = $inXML ; # Get last created RC $RC = $self->{RC}->{$Type} ; } else { # Update afpbuffer $afpbuffer .= $rawtype ; # Destroy last control record if needed and create new one delete $self->{RC}->{$Type} if (defined($self->{RC}->{$Type})); # Get new RC object $RC = new AFPDS::ControlRecord( $Type,$texbase,$AfpName,$file ); &Debug("Found a control record type '$Type'"); } # Check object creation &Abort(115, $file, $Type , $texbase ) unless (defined($RC)); $buffer = "" ; # Initialization should return the length of the record control # Else it should return 0 to stop reading as record type is unknown $tmp = defined($RC) ? $RC->get_required_size() + $readmode : 0 ; &Abort(113,$tmp,$Type) unless ($tmp); # Read now ControlRecord and analyse it if ( $tmp > 0 and read( $AFPDSFILE, $buffer, $tmp ) == $tmp ) { &debugbuffer($buffer); &Abort(300,$Type,$RC->geterror()) if ( defined($RC) and $RC->init( $buffer ) < 0 ); # Check initialization is done &Abort( 116, $Type, sprintf( "%02X" x length($buffer), map { ord } split( //, $buffer ) ) ) unless (defined($RC)); # Set job_tag for this RC library $RC->job_tag($Job); # Check if we have reached a afpds splitting event if ( ! @skip and $RC->can_split_job() ) { # Compute the pos in file for the next job to convert my $pos = tell($AFPDSFILE) - length($afpbuffer) - length($buffer); # Get a tag my @split_tags = $RC->get_split_tags ; # Set this job is the last of a split event $afpds->updateEnv( { # Set SPLIT_FILE with first position in file and length SPLIT_FILE => $startpos . ':' . $pos, SPLIT_TAG => $split_tags[0], SPLIT_TAG2 => $split_tags[1] } ); # Skip the rest to leave as a splitting event push @skip, 'splitted' ; # Finish that job last ; } if ( ! @skip and ($Type eq '001' or $Type eq '100')) { my $pagedef = $RC->getpagedef() ; &UPSTAT('USEPAGEDEF_' . $pagedef ); # Load the perl Library for the PAGEDEF only on record # Type 001 or 100 my $loadlib = "perllib::p1" . lc( $pagedef ); # Abort if we won't be able to load perl module if ($pagedef) { &Abort(104,$pagedef,$loadlib) unless ( grep { -s $_ . '/perllib/p1' . lc( $pagedef ) . '.pm' } @INC ); } else { &Abort(6); } unless (@skip) { &Debug("Loading library with '$loadlib' evaluation"); eval "use " . $loadlib ; $afpds->setpagedef( $pagedef ); $afpds->setpclcond( $RC->ispcloutput() ); $afpds->setformdef( $RC->getformdef() ); $afpds->setoutfile( $RC->getoutfile() ); $afpds->setTRCforTeX( $RC->getTRCforTeX()); &Debug("Perl library loaded"); } # Close previous file handles if it's not the first job $self->CloseFiles if ($GenFlag); $GenFlag = $self->OpenFiles( $RC->getoutfile() ); } unless (@skip) { # Env is updated in Flux and will be returned to JobManager # when completly analysed $afpds->updateEnv( $RC->getenv() ); # Update ArchFile with RECORD content when necessary local $\ = "\n" ; print $ArchFile $RC->getRecord if (defined($ArchFile) and $RC->getRecord()); # Keep a record copy in our RC hash $self->{RC}->{$Type} = $RC ; } # Handle not sized Control Record type 200-202 } elsif ($tmp < 0 and $Type =~ /^20[0-2]$/) { my $line = "" ; # Read till the end of line &myreadline( \*$AFPDSFILE , \$line ); # Update afpbuffer (first char always added at loop begining) $afpbuffer .= substr($line,($inXML and $Type eq '201')?1:0) ; &mychomp( \$line ); # Check record if ( $Type eq '202' ) { # Stopping an input # Forget any information just behind RC 202 string if ($line !~ /^$/) { &from_to( $line , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); &Info(_job_tag."RC 202 tagged with '$line'"); } # Check last record 100 to set the PDF name and service flag &Abort(120, $Type , 100) unless (defined($self->{RC}->{100})); # Check last record 200 to get the working folder &Abort(120, $Type , 200) unless (defined($self->{RC}->{200})); # Check base directory availability for pdf file my $base = @skip ? undef : $self->{RC}->{100}->getbase ; &Abort(122, $base) unless ( defined($base) and $base and -d $base ); my $outbase = $base . '/' . $LOCKID . '_' . $Job ; my $env = { 'PDFFILE' => $outbase . "-$file.pdf", 'XML_A2P' => $outbase . "_a2p-$file.xml", 'XML_CONTENT' => $outbase . "_service-$file.xml" } ; # Update a2p xml attributs before saving my @attribut = ( pdf => $env->{PDFFILE} ); unless (@skip) { $self->{RC}->{200}->set_a2p_attribut(@attribut) or &Abort(123 , @attribut); } @attribut = ( file => $env->{XML_CONTENT} ); unless (@skip) { $self->{RC}->{200}->set_a2p_attribut(@attribut) or &Abort(123 , @attribut); } # Save A2P xml unless (@skip) { $self->{RC}->{200}->save_a2pxml_file($env->{XML_A2P}) or &Abort(121 , 200, $env->{XML_A2P}); } # Check last record 201 to save the content &Abort(120, $Type , 201) unless (defined($self->{RC}->{201})); # Save RC 201 content unless (@skip) { my $content = $env->{XML_CONTENT} ; $self->{RC}->{201}->save_a2pxml_file($content) or &Abort( 121 , 201, $content ); } # Tag Flux as e-service and not arch &Debug("Updating env to handle content and a2p xml files"); $afpds->updateEnv( $env ) unless (@skip); &Debug("Service is ready to handle E-Service request"); # Release inXML flag $inXML = 0 ; } elsif ( $Type eq '201' ) { if ($inXML) { &from_to( $line , $FROM_CONVERT , $TO_CONVERT ) if ($CONVERT_CONTENT_FILE and $DO_CONVERT); # Add EOL to line $line .= "\n" ; # Add the complete line to content $RC->addcontentref( \$line ) unless (@skip); &Debug("Added '$line' to content file"); } else { # Forget any information just behind RC 201 string if ($line !~ /^$/) { &from_to( $line , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); &Info(_job_tag."RC 201 tagged with '$line'"); } # Flag we are inXML handling $inXML = $Type ; } } elsif ( $Type eq '200' ) { # Check last record 100 to get the working folder &Abort(120, $Type , 100) unless (defined($self->{RC}->{100})); unless (@skip) { my $basefile = $self->{RC}->{100}->getoutfile() ; # Update $line = $begin . $line if $inXML ; &from_to( $line , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); $RC->seta2pxml( $line , $basefile ); # Tag Flux as e-service unless ($inXML) { &Debug("Updating env with expected values"); $afpds->updateEnv( { DO_ESERVICE => 'yes', DO_PDF => 'yes', } ); } # Flag we are inXML handling if not complete and valid $inXML = $RC->a2pxml_is_valid() || $Type ; # Remark: service name '200' is an error as prohibited # (checked in XML.pm) if ($inXML and $inXML ne $Type) { &Debug("Updating env with requested service"); $afpds->updateEnv( { E_SERVICE => $inXML } ); # Release inXML flag $inXML = 0 ; } } } # Keep a record copy in our RC hash $self->{RC}->{$Type} = $RC unless (@skip); } else { # Bad length read for that record &debugbuffer($buffer); &Abort(102,$Type); } # Update afpbuffer $afpbuffer .= $buffer ; # End of ControlRecord management } else { # This is a standard line, first char is the control char $cctrl = $buffer ; &from_to( $cctrl , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); &Debug("Found standard line with control char <$cctrl>"); # The next char is used to select TRC font read( $AFPDSFILE , $char , 1 ); # Update afpbuffer $afpbuffer .= $char ; # Check AFPDS Flux is defined if (defined($self->{AFPDS})) { # ErFo001 Fix: Select TRC font $self->{AFPDS}->selectTRCfont( ord($char) ) unless (@skip); # Read the rest of the line $buffer = ""; $long = &myreadline( \*$AFPDSFILE , \$buffer ); # Update afpbuffer $afpbuffer .= $buffer ; $tmp = &compute( \$buffer , -2 , 2 ); if ( $tmp != 0x0D0A ) { # Buffer must end with 0x0D0A &debugbuffer( $buffer ); &Abort(101,$tmp); } } else { &Abort(5); } &mychomp( \$buffer ); $long = length( $buffer ) ; &from_to( $buffer , $FROM_CONVERT , $TO_CONVERT ) if ($DO_CONVERT); # Compute the new AFP line $self->{AFPDS}->afpline( $cctrl , $buffer ) unless (@skip); } my $currentpos = tell($AFPDSFILE) ; my $currenttime = &tv_interval(\@StartTime) ; # Return convertion status after required timing if ( $currenttime >= $tv_timing ) { my $fileratio = $currentpos/$EOF ; # Compute convertion ratio my $ratio = sprintf("%d",$fileratio*100); # Compute termination estimation my $remain = sprintf("%.1f",$currenttime/$fileratio-$currenttime); &Debug("Convertion done at $ratio%, should finish in $remain s"); # Return information to JobManager $self->Return( $Job => &GetCom( comINF , 'AFP2TeX Conversion status' => "$ratio% done, $remain sec remaining" )); # Update job state in spool &a2pjobstate( $Job, 2, '.' , { STATUS => $ratio.'%' }) or &Info("Can't update status conversion progress to $ratio%"); # Update next timing report $tv_timing += $CONV_STATUS_TIMING ; } &Debug("Loop at AFPDS file off-set " . sprintf("%Xh",$currentpos) . ($inXML?" in not sized RC '$inXML'":"")); $buffer = getc( $AFPDSFILE ) ; } # End while read file &Debug("File#" . $file . ": Last TeX file generation"); # Output last converted TeX file &Abort(2) unless ( @skip or &generateTeX ); # Keep stats on conversion rate my $tv_interval = &tv_interval($RateTimer) ; &MAXSTAT('CONV-RATE',int( 100 * $file / $tv_interval ) / 100) if ($tv_interval); ABORTED_FILE: # Freeing objects delete $self->{AFPDS} if (defined($self->{AFPDS}) and ! $self->{AFPDS}->issplitted()); delete $self->{RC} if ( defined($self->{RC}) ); # Reset vars $file = 0 ; return $ErrFlag ? &End($ErrFlag) : &End(0, "Conversion done" ); } my ( $conv_time , $conv_count ) = ( 0, 0 ); sub generateTeX { my $afpds = $self->{AFPDS} ; # Check few things on RC # At least a RC 001 or a RC 100 must have been provided return &Error(_job_tag."No RC 001 or RC 100 defined") unless (defined($self->{RC}->{'001'}) or defined($self->{RC}->{'100'})); # If a RC 200 has been provided, we must also have a RC 201 and a RC 202 return &Error(_job_tag."Found wrong combination of record 200-202") if (defined($self->{RC}->{'200'}) and $self->{RC}->{'200'}->a2pxml_is_valid and !(defined($self->{RC}->{'201'}) and defined($self->{RC}->{'202'}))); # Output the convertex TeX file return &Error(_job_tag."Error while generating TeX file") unless ($afpds->gentex( $TexFile )); # Output to AfpFile and check TeX convertion rate if (defined($afpbuffer) and length($afpbuffer)) { # 1. Output to AfpFile if (eof($AFPDSFILE)) { print $AfpFile $afpbuffer if (defined($AfpFile)); } else { # Output AfpFile without the last char '#' print $AfpFile substr($afpbuffer,0,-1) if (defined($AfpFile)); # And reset afpbuffer with only char '#' ( in ebcdic) $afpbuffer = chr(0x7B); } # 2. Wait a little to match required TeX convertion rate # essentially on big job if ( $MAX_CONV_RATE and $file > $MAX_CONV_RATE ) { my $prev_conv_time = $conv_time ; # Default to zero # Still sleep a little if we can if ( $conv_time ) { &MAXSTAT('CONV-RATE-PRE-SLEEP-TIME',$conv_time); usleep $conv_time ; } # Get the delay since the beginning of that conversion $conv_time = &tv_interval($RateTimer) * 1_000_000 ; # min_conv_time is the time by conversion to reach computed before # each conversion start my $diff = $conv_time - $file * $min_conv_time ; my $count = $file > $conv_count ? $file - $conv_count : 1 ; if ( $diff < 0 ) { # Get the time we should have sleep to reach max_conv_rate my $usleep = abs($diff) ; # Really not sleep if time to sleep is too short unless ( $usleep < $USLEEP ) { &UPSTAT('CONV-RATE-SLEEPING'); &MAXSTAT('CONV-RATE-SLEEP-TIME',$usleep); &Debug("Waiting $usleep ms to lower speed as " . $MAX_CONV_RATE . " Max conversion rate was reached"); usleep $usleep ; } $conv_time = $usleep / $count ; } elsif ($prev_conv_time) { &UPSTAT('CONV-RATE-SLEPT-TOO-MUCH'); # diff is the excess time we have slept my $correction = $count > 0 ? $diff / $count : 0 ; # Always reset $conv_time to keep the sleep next time if ( $correction > 0 and $correction < $prev_conv_time and $conv_time > $USLEEP ) { # Adjust conv_time to sleep less next time $conv_time = $prev_conv_time - $correction ; } else { $conv_time = $prev_conv_time ; } } # Remember conversion count $conv_count = $file ; } } # Return the job environment to JobManager &Debug("File#" . $file . ": Shell env file generation"); # Set MTIME job env to file mtime $afpds->updateEnv( { MTIME => $mtime } ); # Announce last job to master thread $self->Return( $Job , $afpds->getEnv() ); # And keep some stats $self->KeepStats( $Job, $afpds->getstat() ); # Update job state in spool &a2pjobstate( $Job, 2, 'o' ) or &Info(_job_tag."Can't update status to step 2 done"); # Return true 1 ; } ################################################################################ sub End { # End jobs here as it closes properly any opened file my $ret = shift || 0 ; my $msg = shift || ""; if (defined($msg)) { $ret and $msg ? &Error($msg) : &Debug($msg) ; } # Now clean everything &Debug("Closing files"); &CloseFiles(); close($AFPDSFILE) if (defined($AFPDSFILE)); return $ErrFlag ? $ErrFlag : $ret ; } our $AbortMessages = { 0 => "Unknown error", 1 => "Last TeX file not generated", 2 => "Can't generate TeX file", 3 => "Can't handle RC 5A without AFPDS flux defined", 4 => "Expected defined AFPDS flux here", 5 => "Can't processing standard AFP line without AFPDS Flux defined", 6 => "Can't load PAGEDEF as it isn't defined", 100 => "Can't use not defined '%s' controlrecord", 101 => "Found AFPDS text line with bad end of line <0x%04X>", 102 => "Unknown control record '%s'", 103 => "File#%s: Bad TeX file generation for print %s", 104 => "Unable to load PAGEDEF %s as Perl module %s not found", 105 => "freed error number", 106 => "Can't create %s folder: %s", 107 => "Can't open %s: %s", 108 => "Can't process %s in binary mode: %s", 109 => "Can't read last 10 chars of %s: %s", 110 => "Found AFPDS Field with bad end of line <0x%04X>", 111 => "Found AFPDS Field with bad length: %s vs %s", 112 => "Bad control record type format found for record type '%s'", 113 => "Bad control record length (%s) for record type '%s'", 114 => "Got bad AFPDS format as RC 5A is too short (%s < 8)", 115 => "Can't create RC object %d of type '%s' with TEXBASE '%s'", 116 => "Can't initialize RC type '%s' with buffer '%s'", 117 => "Can't use RC type '%s' on with no AFPDS Flux defined", 118 => "AFP error on RC 5A with ident %6X, flag %08b, reserved 0x%04X, ". "long %d and buffer %s", 119 => "Can't use existing folder '%s' to compute AFPDS file", 120 => "Can't use RC type %d without previously defined RC type %d", 121 => "Can't save XML content for RC type %d in %s", 122 => "Folder '%s' not available", 123 => "Can't update '%s' attribut with value '%s' in A2P XML", 124 => "File '%s' is too short or corrupted", 200 => "Got RC 5A error %d, %s", 300 => "Got RC #%s# init error %d, %s", 500 => "trying to convert job %d after errors" }; my @AbortErrors = ( 0, 2, 3, 4, 5, 106, 107, 108, 109, 119, 122 ); # Must be called with number identifying message and then abort processing sub Abort { $ErrFlag = shift || 0 ; unless ( defined($skip[$ErrFlag]) and $skip[$ErrFlag]++ ) { @AbortTime = &gettimeofday(); my $file_offset = sprintf("AFPDS file off-set %Xh",tell($AFPDSFILE)) ; my @ERR = $ErrFlag >= 500 ? () : ( _job_tag . "Aborting at $file_offset" ) ; my $msg = $ErrFlag >= 500 ? $file_offset.": " : "" ; $msg .= _job_tag . $AbortMessages->{$ErrFlag} ; my $ABTERM = $ErrFlag < 100 ? $msg." (Err$ErrFlag)" : sprintf( $msg." (Err%d)" , @_, $ErrFlag ) ; # Still return the ABTERM message to JobManager $self->Return( $Job => &GetCom( comINF , 'ABTERM '.$ErrFlag, $ABTERM )); push @ERR, $ABTERM ; $ErrFlag < 500 ? &Error( @ERR ) : &Warn( @ERR ); # Return information to JobManager map { $self->Return( $Job => &GetCom( comINF , 'ERROR' , $_ )) } @ERR ; # Update status for afpjob map { &a2pjobstate( $Job, 2, 'A', { ABTERM => "$_" } ) } @ERR ; # Keep the ABTERM on the current JID if really started if ( $file ) { # Update status for subjob designed by JID key in %Infos my $Infos = { JID => $Job . "-" . $file } ; $Infos->{STATUS} = 'ABTERM' if ( $ErrFlag < 500 ); map { $Infos->{ERRORS} = "$_" ; &a2pjobstate( $Job, 2, 'A', $Infos ); } @ERR } } if (grep { $_ == $ErrFlag } @AbortErrors) { goto ABORTED_FILE ; } else { # Don't Abort file conversion to try next sub jobs return $ErrFlag ; } } END { if ($ErrFlag and @AbortTime) { my $time = sprintf("%.2f sec",&tv_interval(\@AbortTime)); &Error("Last conversion aborted $time ago with $ErrFlag abort number"); } } &Debug("Module " . __PACKAGE__ . " v$VERSION loaded"); 1;