--- loncom/interface/printout.pl 2006/12/19 10:39:28 1.119 +++ loncom/interface/printout.pl 2008/03/18 09:48:11 1.133 @@ -1,6 +1,7 @@ #!/usr/bin/perl # CGI-script to run LaTeX, dvips, ps2ps, ps2pdf etc. # +# $Id: printout.pl,v 1.133 2008/03/18 09:48:11 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -38,9 +39,11 @@ use Apache::loncommon(); use Apache::lonlocal; use Apache::lonmsg(); use LONCAPA::Enrollment; +use LONCAPA::Configuration; use strict; +my $busy_wait_timeout = 30; # Determine if a user is operating as a student for this course/domain. #Parameters: @@ -127,8 +130,8 @@ sub send_error_mail { foreach my $line (@$texfile) { $message .= "$line\n"; } - my (undef, %receivers) = &Apache::lonfeedback::decide_receiver(undef, 0, - 1,1,1); + my (undef, %receivers) = &Apache::lonmsg::decide_receiver(undef, 0, + 1,1,1); # print "
sending...section: $env{'request.course.sec'}"; foreach my $dest (keys %receivers) { # print "
dest is $dest"; @@ -160,144 +163,81 @@ Your cookie information is incorrect. END return; } - &Apache::lonlocal::get_language_handle(); - &Apache::loncommon::content_type(undef,'text/html'); - print(&Apache::loncommon::start_page('Creating PDF')); +my %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')}; +&Apache::lonlocal::get_language_handle(); +&Apache::loncommon::content_type(undef,'text/html'); +$env{'request.noversionuri'} = '/cgi-bin/printout.pl'; +print(&Apache::loncommon::start_page('Creating PDF')); + +my $identifier = $ENV{'QUERY_STRING'}; +my $texfile = $env{'cgi.'.$identifier.'.file'}; +my $laystyle = $env{'cgi.'.$identifier.'.layout'}; +my $numberofcolumns = $env{'cgi.'.$identifier.'.numcol'}; +my $paper = $env{'cgi.'.$identifier.'.paper'}; +my $selectionmade = $env{'cgi.'.$identifier.'.selection'}; +my $tableofcontents = $env{'cgi.'.$identifier.'.tableofcontents'}; +my $tableofindex = $env{'cgi.'.$identifier.'.tableofindex'}; +my $advanced_role = $env{'cgi.'.$identifier.'.role'}; +my $number_of_files = $env{'cgi.'.$identifier.'.numberoffiles'}+1; +my $student_names = $env{'cgi.'.$identifier.'.studentnames'}; +my $backref = &Apache::lonnet::unescape($env{'cgi.'.$identifier.'.backref'}); + + +my @names_pack=(); +if ($student_names=~/_END_/) { + @names_pack=split(/_ENDPERSON_/,$student_names); +} +if ($backref) { + print('

'.&mt("[_1]Return[_2] to editing resource.", + "","").'

'); +} +my $figfile = $texfile; +$figfile =~ s/^(.*_printout)_\d+_\d+_\d+\.tex/$1\.dat/; +my $duefile = $texfile; +$duefile =~ s/^(.*_printout)_\d+_\d+_\d+\.tex/$1\.due/; - my $identifier = $ENV{'QUERY_STRING'}; - my $texfile = $env{'cgi.'.$identifier.'.file'}; - my $laystyle = $env{'cgi.'.$identifier.'.layout'}; - my $numberofcolumns = $env{'cgi.'.$identifier.'.numcol'}; - my $paper = $env{'cgi.'.$identifier.'.paper'}; - my $selectionmade = $env{'cgi.'.$identifier.'.selection'}; - my $tableofcontents = $env{'cgi.'.$identifier.'.tableofcontents'}; - my $tableofindex = $env{'cgi.'.$identifier.'.tableofindex'}; - my $advanced_role = $env{'cgi.'.$identifier.'.role'}; - my $number_of_files = $env{'cgi.'.$identifier.'.numberoffiles'}+1; - my $student_names = $env{'cgi.'.$identifier.'.studentnames'}; - my $backref = &Apache::lonnet::unescape($env{'cgi.'.$identifier.'.backref'}); - - my @names_pack=(); - if ($student_names=~/_END_/) { - @names_pack=split(/_ENDPERSON_/,$student_names); - } +#------------------------------------------------------------------------------------- +# +# Each print may have associated with it a file that contains a set of figures +# that need to be converted to .eps from whatever form they were in when included +# in the resource. The name of the figure file is in $figfile. If it exists, +# it contains the names of the files that need to be converted, one per line. +# + +if (-e $figfile) { + # print "$figfile exists\n"; + my %done_conversion; + my $temporary_file=IO::File->new($figfile) || die "Couldn't open fig file $figfile for reading: $!\n"; + my @content_of_file = <$temporary_file>; + close $temporary_file; + my $noteps; + my %prog_state; + if ($advanced_role) { %prog_state=&Apache::lonhtmlcommon::Create_PrgWin('','Converting Images to EPS','Picture Conversion Status',$#content_of_file,'inline','80'); } + print('
'); + foreach my $not_eps (@content_of_file) { + chomp($not_eps); + if ($not_eps ne '') { + $not_eps=~s|\/\.\/|\/|g; + if (!$done_conversion{$not_eps}) { # Only convert multiple includes once. + &convert_figure($not_eps); + $done_conversion{$not_eps} = 1; + } + } + } + if ($advanced_role) { + &Apache::lonhtmlcommon::Close_PrgWin('',\%prog_state); + } + unlink($figfile); +} +# End of figure conversion section: +# +#-------------------------------------------------------------------------------------------- + -print "Return to last resource.

"; - my $figfile = $texfile; - $figfile =~ s/^([^\.]+printout)[^t]+\.tex/$1\.dat/; - my $duefile = $texfile; - $duefile =~ s/^([^\.]+printout)[^t]+\.tex/$1\.due/; - #do we have figures? - # print "Figure file: $figfile\n"; - if (-e $figfile) { - # print "$figfile exists\n"; - my %done_conversion; - my $temporary_file=IO::File->new($figfile) || die "Couldn't open fig file $figfile for reading: $!\n"; - my @content_of_file = <$temporary_file>; - close $temporary_file; - my $noteps; - my %prog_state; - if ($advanced_role) { %prog_state=&Apache::lonhtmlcommon::Create_PrgWin('','Coverting Images to EPS','Picture Conversion Status',$#content_of_file,'inline','80'); } - print('
'); - foreach my $not_eps (@content_of_file) { - chomp($not_eps); - if ($not_eps ne '') { - # print "Converting $not_eps"; # Debugging. - my $status_statement='EPS picture for '.$not_eps; - # print "$status_statement\n"; - $not_eps=~s|\/\.\/|\/|g; - my $eps_f = $not_eps; - # $eps_f =~ s/\.[^.]*$/\.eps/i; - if ($eps_f=~/\/home\/([^\/]+)\/public_html\//) { - $eps_f=~s/\/home\/([^\/]+)\/public_html/$1/; - $eps_f = '/home/httpd/prtspool/'.$eps_f; - } elsif ($eps_f=~/$Apache::lonnet::perlvar{'lonDocRoot'}\/res\//) { - $eps_f=~m/$Apache::lonnet::perlvar{'lonDocRoot'}\/res\/(.+)/; - $eps_f = '/home/httpd/prtspool/'.$1; - } elsif ($eps_f=~/$Apache::lonnet::perlvar{'lonUsersDir'}\//) { - $eps_f=~/$Apache::lonnet::perlvar{'lonUsersDir'}\/([^\/]+)\/\w\/\w\/\w\/(.+)/; - $eps_f = '/home/httpd/prtspool/'.$1.'/'.$2; - } - $eps_f =~ s/ /\_/g; # Spaces are problematic for system commands and LaTeX. - # - # If the file is already an .eps or .ps file, - # We really just need to copy it from where it was to prtspool - # but with the spaces substituted to _'s. - # - my ($nsname,$path, $sext) = &fileparse($eps_f, qr/\.(ps|eps)/i); - if ($sext =~/ps$/i) { - # print "$not_eps is a postscript file. copy to $path\n"; - &File::Path::mkpath($path,0,0777); - #print("Made path: $path"); - #$not_eps =~ s/^\s+//; - #$not_eps =~ s/\s+$//; - #$not_eps =~ s/ /\__/g; - #print("Copying $not_eps to $eps_f\n"); - copy("$not_eps", "$eps_f"); - # print "Copy complete\n"; - } else { - - $eps_f .= '.eps'; # Just append the eps ext. - my $path=$eps_f; - $path =~ s/\/([^\/]+)\.eps$//; - # print "Final file path: $path "; # Debugging - &File::Path::mkpath($path,0,0777); - $not_eps =~ s/^\s+//; - $not_eps =~ s/\s+$//; - $not_eps =~ s/ /\\ /g; - if ( exists($done_conversion{$not_eps})) { next; } - if ($advanced_role) { - my $prettyname=$not_eps; - $prettyname=~s|/home/([^/]+)/public_html|/priv/$1|; - $prettyname=~s|$Apache::lonnet::perlvar{'lonDocRoot'}/|/|; - &Apache::lonhtmlcommon::Update_PrgWin('',\%prog_state, - 'Converting to EPS '.$prettyname); - } - $done_conversion{$not_eps}=1; - # print "Converting $not_eps -> $eps_f"; # Debugging - system("convert $not_eps $eps_f"); - # check is eps exist in prtspool - if (not -e $eps_f) { - # converting an animated gif creates either: - # anim.gif.eps.0 - # or - # anim.gif-0.eps - for (my $i=0;$i<10000;$i++) { - if (-e $eps_f.'.'.$i) { - rename($eps_f.'.'.$i, $eps_f); - last; - } - my $anim_eps = $eps_f; - $anim_eps =~ s/(\.[^.]*)\.eps$/$1-$i\.eps/i; - if (-e $anim_eps) { - rename($anim_eps, $eps_f); - last; - } - } - } - - # imagemagick 6.2.0-6.2.7 fails to properly handle - # convert anim.gif anim.gif.eps - # it creates anim.eps instead. - if (not -e $eps_f) { - my $eps_f2 = $eps_f; - $eps_f2 =~ s/\.[^.]*\.eps$/\.eps/i; - if(-e $eps_f2) { - rename($eps_f2,$eps_f); - } - } - } - } - } - if ($advanced_role) { - &Apache::lonhtmlcommon::Close_PrgWin('',\%prog_state); - } - unlink($figfile); - } #print "$texfile\n"; #name of the tex file for debugging only my @texfile=($texfile); if ($number_of_files>1) { @@ -371,17 +311,17 @@ foreach $texfile (@texfile) { my $name_file = $2; my $path_file = $1.'/'; chdir $path_file; - my $dvi_file= $name_file; $dvi_file =~ s/\.tex/$name_range\.dvi/; + my $dvi_file= $name_file; $dvi_file =~ s/\.tex$/\.dvi/; &busy_wait_command("latex $name_file 1>/dev/null 2>/dev/null", "for $status_statement now LaTeXing file", - \%prog_state,$dvi_file); + \%prog_state,$dvi_file, $busy_wait_timeout); if ($tableofcontents eq 'yes') { &busy_wait_command("latex $name_file 1>/dev/null 2>/dev/null", "for $status_statement First LaTeX of file for table of contents", - \%prog_state,$dvi_file); + \%prog_state,$dvi_file, $busy_wait_timeout); &busy_wait_command("latex $name_file 1>/dev/null 2>/dev/null", "for $status_statement Second LaTeX of file for table of contents", - \%prog_state,$dvi_file); + \%prog_state,$dvi_file,$busy_wait_timeout); } #to create table of contents my $idxname=$name_file; $idxname=~s/\.tex$/\.idx/; @@ -391,7 +331,7 @@ foreach $texfile (@texfile) { \%prog_state,$idxname); &busy_wait_command("latex $name_file 1>/dev/null 2>/dev/null", "for $status_statement now LaTeXing file for index section", - \%prog_state,$dvi_file); + \%prog_state,$dvi_file, $busy_wait_timeout); } #to create index #Do we have a latex error in the log file? my $logfilename = $texfile; $logfilename =~ s/\.tex$/\.log/; @@ -440,10 +380,10 @@ foreach $texfile (@texfile) { print "\n"; # print "
Advanced role
"; print "The link to "; - $logfilename=~s/\/home\/httpd//; + $logfilename=~s{^\Q$perlvar{'lonPrtDir'}\E}{/prtspool}; print "Your log file "; print "\n"; - #link tooriginal LaTeX file (included according Michael Hamlin desire) + #link to original LaTeX file my $tex_temporary_file=IO::File->new($texfile) || die "Couldn't open tex file $texfile for reading: $!\n"; my @tex_content_of_file = <$tex_temporary_file>; close $tex_temporary_file; @@ -453,7 +393,7 @@ foreach $texfile (@texfile) { print $tex_temporary_file 'LOGFILE
'.$body_tex_file.'
'."\n"; print "

"; print "The link to "; - $texfile=~s/\/home\/httpd//; + $texfile=~s{^\Q$perlvar{'lonPrtDir'}\E}{/prtspool}; print "Your original LaTeX file "; print "\n"; my $help_text = &Apache::loncommon::help_open_topic("Print_Resource", "Help on printing"); @@ -507,24 +447,24 @@ foreach $texfile (@texfile) { # &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement first latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file,$busy_wait_timeout); if ($tableofcontents eq 'yes') { &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement second latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file,$busy_wait_timeout); &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement third latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file,$busy_wait_timeout); } if ($tableofindex eq 'yes') { my $idxname = $latex_file; $idxname =~ s/\.tex$/\.idx/; - &busy_wait_command("makindex $idxname", + &busy_wait_command("makeindex $idxname", "Re-creating index file", \%prog_state, $idxname); &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement now Recreting index (latex)", - \%prog_state, $dvi_file); + \%prog_state, $dvi_file,$busy_wait_timeout); } &busy_wait_command("$comma $name_file 1>/dev/null 2>/dev/null", @@ -558,7 +498,7 @@ foreach $texfile (@texfile) { #print("
Missing fonts file is: $missfonts_file"); if (-e $missfonts_file) { #print("
Missing fonts file exists\n"); - &create_missing_fonts($missfonts_file); + &create_missing_fonts($missfonts_file,\%prog_state); &busy_wait_command("$comma $name_file 1>/dev/null 2>/dev/null", "for $status_statement dvips generated missing fonts", \%prog_state, $new_name_file); @@ -603,18 +543,18 @@ foreach $texfile (@texfile) { $texps =~ s/\.tex/\.ps/; my @garb = ($texaux,$texdvi,$texps); # unlink @garb; - unlink $duefile; + unlink($duefile); print "Your PDF document"; - unlink $missfonts_file; + unlink($missfonts_file); } if ($advanced_role) { print "

"; print "The link to "; - $logfilename=~s/\/home\/httpd//; + $logfilename=~s{^\Q$perlvar{'lonPrtDir'}\E}{/prtspool}; print "Your log file "; print "\n"; - #link tooriginal LaTeX file (included according Michael Hamlin desire) + #link to original LaTeX file my $tex_temporary_file=IO::File->new($texfile) || die "Couldn't open tex file $texfile for reading: $!\n"; my @tex_content_of_file = <$tex_temporary_file>; close $tex_temporary_file; @@ -624,7 +564,7 @@ foreach $texfile (@texfile) { print $tex_temporary_file 'LOGFILE
'.$body_tex_file.'
'."\n"; print "

"; print "The link to "; - $texfile=~s/\/home\/httpd//; + $texfile=~s{^\Q$perlvar{'lonPrtDir'}\E}{/prtspool}; print "Your original LaTeX file "; print "\n"; } @@ -669,7 +609,7 @@ foreach $texfile (@texfile) { #print("
Missing fonts file is: $missfonts_file"); if (-e $missfonts_file) { #print("
Missing fonts file exists\n"); - &create_missing_fonts($missfonts_file); + &create_missing_fonts($missfonts_file,\%prog_state); &busy_wait_command("$comma $name_file 1>/dev/null 2>/dev/null", "for $status_statement dvips generated missing fonts", \%prog_state, $new_name_file); @@ -680,14 +620,14 @@ foreach $texfile (@texfile) { &repaginate($new_name_file, $latex_file, $numberofcolumns); &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement first latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file, $busy_wait_timeout); if ($tableofcontents eq 'yes') { &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement second latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file, $busy_wait_timeout); &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement third latex to repaginate", - \%prog_state, $name_file); + \%prog_state, $name_file, $busy_wait_timeout); } if ($tableofindex eq 'yes') { my $idxname = $latex_file; @@ -697,7 +637,7 @@ foreach $texfile (@texfile) { \%prog_state, $idxname); &busy_wait_command("latex $latex_file 1>/dev/null 2>/dev/null", "for $status_statement now Recreting index (latex)", - \%prog_state, $dvi_file); + \%prog_state, $dvi_file, $busy_wait_timeout); } &busy_wait_command("$comma $name_file 1>/dev/null 2>/dev/null", "for $status_statement dvips to repaginate", @@ -756,11 +696,11 @@ foreach $texfile (@texfile) { $texps =~ s/\.tex/\.ps/; my @garb = ($texlog,$texaux,$texdvi,$texps); # unlink @garb; - unlink $duefile; + unlink($duefile); print "$link_text - click here to download pdf"; print "\n"; } - unlink $missfonts_file; + unlink($missfonts_file); } } else { @@ -779,7 +719,7 @@ if ($number_of_files>1) { print("
Zip Output:\n");
     system($statement);
     print("
"); - $zipfile=~s/\/home\/httpd//; + $zipfile=~s{^\Q$perlvar{'lonPrtDir'}\E}{/prtspool}; print "
A ZIP file of all the PDFs."; } if ($advanced_role) { &Apache::lonhtmlcommon::Close_PrgWin('',\%prog_state); } @@ -789,9 +729,17 @@ my $done; sub REAPER { $done=1; } - +# +# Execute a command updating the status window as the command's +# output file builds up (at intervals of a second). +# +# If the timeout argument defined, then if that many seconds +# elapses without an increase in the size of the output file, +# the command will be killed (this deals with the case when +# latex crawls into an infinite loop). +# sub busy_wait_command { - my ($command,$message,$progress_win,$output_file)=@_; + my ($command,$message,$progress_win,$output_file, $timeout)=@_; $SIG{CHLD} = \&REAPER; $done=0; @@ -799,12 +747,30 @@ sub busy_wait_command { if ($advanced_role) { &Apache::lonhtmlcommon::Update_PrgWin('',$progress_win,$message); } + my $last_size = 0; + my $unchanged_time = 0; while(!$done) { sleep 1; my $extra_msg; if ($output_file) { my $size=(stat($output_file))[7]; $extra_msg=", $size bytes generated"; + if ($size == $last_size) { + $unchanged_time++; + if ($timeout && ($unchanged_time > $timeout)) { + print "

Operation timed out!

\n"; + print "

Executing $command, the output file $output_file did not grow\n"; + print "after $timeout seconds. This may indicate $command\n"; + print "is in an infinite loop.\n"; + print "See if printing fewer copies helps. Please contact LON-CAPA\n"; + print "support about this in any event."; + print "

"; + kill(9, $pid); # Reaper will do the rest...I hope there's errors in the log. + } + } else { + $last_size = $size; + $unchanged_time = 0; + } } if ($advanced_role) { &Apache::lonhtmlcommon::Update_PrgWin('',$progress_win, @@ -864,8 +830,8 @@ sub repaginate { # bottom of the page, m the page number within the document. # - if ($line =~ /^%%Page:/) { - my @pageinfo = split(/ /, $line); + if ($line =~ /^%%Page:\s+\d+\s+\d+/) { + my @pageinfo = split(/\s+/, $line); if ($page_number < $pageinfo[1]) { $page_number = $pageinfo[1]; } elsif ($pageinfo[2] ne 1) { @@ -996,8 +962,90 @@ sub create_missing_fonts { #print("
Creating fonts via command: $command"); &busy_wait_command("$command 1>/dev/null 2>/dev/null", "Creating missing font", - $state, $command); + $state); } } +# +# Convert a figure file to encapsulated postscript: +# At present, this is using a lot of file scoped globals to pass data around. +# Parameters: +# not_eps - The name of the file to convert which, presumably, is not +# already an eps file. +# +sub convert_figure { + my ($not_eps) = @_; + + + my $status_statement='EPS picture for '.$not_eps; + my $eps_f = $not_eps; + if ($eps_f=~/\/home\/([^\/]+)\/public_html\//) { + $eps_f=~s/\/home\/([^\/]+)\/public_html/$1/; + $eps_f = $perlvar{'lonPrtDir'}.'/'.$eps_f; + } elsif ($eps_f=~/$perlvar{'lonDocRoot'}\/res\//) { + $eps_f=~m/$perlvar{'lonDocRoot'}\/res\/(.+)/; + $eps_f = $perlvar{'lonPrtDir'}.'/'.$1; + } elsif ($eps_f=~/$perlvar{'lonUsersDir'}\//) { + $eps_f=~/$perlvar{'lonUsersDir'}\/([^\/]+)\/\w\/\w\/\w\/(.+)/; + $eps_f = $perlvar{'lonPrtDir'}.'/'.$1.'/'.$2; + } + $eps_f =~ s/ /\_/g; # Spaces are problematic for system commands and LaTeX. + # + # If the file is already an .eps or .ps file, + # We really just need to copy it from where it was to prtspool + # but with the spaces substituted to _'s. + # + my ($nsname,$path, $sext) = &fileparse($not_eps, qr/\.(ps|eps)/i); + if ($sext =~/ps$/i) { + &File::Path::mkpath($path,0,0777); + copy("$not_eps", "$eps_f"); + } else { + + $eps_f .= '.eps'; # Just append the eps ext. + my $path=$eps_f; + $path =~ s/\/([^\/]+)\.eps$//; + &File::Path::mkpath($path,0,0777); + $not_eps =~ s/^\s+//; + $not_eps =~ s/\s+$//; + $not_eps =~ s/ /\\ /g; + if ($advanced_role) { + my $prettyname=$not_eps; + $prettyname=~s|/home/([^/]+)/public_html|/priv/$1|; + $prettyname=~s|$perlvar{'lonDocRoot'}/|/|; + &Apache::lonhtmlcommon::Update_PrgWin('',\%prog_state, + 'Converting to EPS '.$prettyname); + } + system("convert $not_eps $eps_f"); + if (not -e $eps_f) { + # converting an animated gif creates either: + # anim.gif.eps.0 + # or + # anim.gif-0.eps + for (my $i=0;$i<10000;$i++) { + if (-e $eps_f.'.'.$i) { + rename($eps_f.'.'.$i, $eps_f); + last; + } + my $anim_eps = $eps_f; + $anim_eps =~ s/(\.[^.]*)\.eps$/$1-$i\.eps/i; + if (-e $anim_eps) { + rename($anim_eps, $eps_f); + last; + } + } + } + + # imagemagick 6.2.0-6.2.7 fails to properly handle + # convert anim.gif anim.gif.eps + # it creates anim.eps instead. + if (not -e $eps_f) { + my $eps_f2 = $eps_f; + $eps_f2 =~ s/\.[^.]*\.eps$/\.eps/i; + if(-e $eps_f2) { + rename($eps_f2,$eps_f); + } + } + } + +}