[LON-CAPA-cvs] cvs: loncom /interface lonspreadsheet.pm

matthew lon-capa-cvs@mail.lon-capa.org
Fri, 08 Nov 2002 15:28:03 -0000


This is a MIME encoded message

--matthew1036769283
Content-Type: text/plain

matthew		Fri Nov  8 10:28:03 2002 EDT

  Modified files:              
    /loncom/interface	lonspreadsheet.pm 
  Log:
  Many minor changes.  This is a check-in before I start reworking the internal
  cache mechanisms.
  &updatestudentassesssheet in to three parts - 
    &get_student_rowlabels, &get_assess_rowlabels, and what remains of 
  &updatestudentassesssheet.  May undo this change in the future....
  Changed a few foreach (keys %hash) to while (.. each (%hash)).
  Some extra logging is currently done, to be removed.
  Comments added to &handler.
  Changed (probably broke) the caching....
  
  
--matthew1036769283
Content-Type: text/plain
Content-Disposition: attachment; filename="matthew-20021108102803.txt"

Index: loncom/interface/lonspreadsheet.pm
diff -u loncom/interface/lonspreadsheet.pm:1.135 loncom/interface/lonspreadsheet.pm:1.136
--- loncom/interface/lonspreadsheet.pm:1.135	Thu Nov  7 10:37:02 2002
+++ loncom/interface/lonspreadsheet.pm	Fri Nov  8 10:28:03 2002
@@ -1,5 +1,5 @@
 #
-# $Id: lonspreadsheet.pm,v 1.135 2002/11/07 15:37:02 matthew Exp $
+# $Id: lonspreadsheet.pm,v 1.136 2002/11/08 15:28:03 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -64,6 +64,7 @@
 use Apache::loncoursedata;
 use Apache::File();
 use Spreadsheet::WriteExcel;
+
 #
 # Caches for coursewide information 
 #
@@ -93,7 +94,7 @@
 my %courserdatas;
 my %userrdatas;
 my %defaultsheets;
-my %updatedata;
+my %rowlabel_cache;
 
 #
 # These global hashes are dependent on user, course and resource, 
@@ -1107,6 +1108,22 @@
     return @exportarray;
 }
 
+
+
+sub update_student_sheet{
+    my $sheet = shift;
+    # Load in the studentcalc sheet
+    &readsheet($sheet,'default_studentcalc');
+    # Determine the structure (contained assessments, etc) of the sheet
+    &updatesheet($sheet);
+    # Load in the cached sheets for this student
+    &cachedssheets($sheet);
+    # Load in the (possibly cached) data from the assessment sheets        
+    &loadstudent($sheet);
+    # Compute the sheet
+    &calcsheet($sheet);
+}
+
 # ========================================================== End of Spreadsheet
 # =============================================================================
 #
@@ -1275,6 +1292,7 @@
     my $rows_output=0;
     foreach my $rownum (@Rows) {
         my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
+        next if ($rowlabel =~ /^\s*$/);
         #
         my $defaultbg='#E0FF';
         #
@@ -1369,6 +1387,7 @@
     my $rows_output=0;
     foreach my $rownum (@Rows) {
         my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
+        next if ($rowlabel =~ /^\s*$/);
         push (@Values,&format_csv_rowlabel($rowlabel));
         foreach my $cell (@rowdata) {
             push (@Values,'"'.$cell->{'value'}.'"');
@@ -1404,6 +1423,7 @@
 ############################################
 sub outsheet_recursive_excel {
     my ($sheet,$r) = @_;
+    my $c = $r->connection;
     return undef if ($sheet->{'sheettype'} ne 'classcalc');
     my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r);
     return undef if (! defined($workbook));
@@ -1414,8 +1434,21 @@
     # Figure out who the students are
     my %f=&getformulas($sheet);
     my $count = 0;
-    $r->print("<br />\n");
+    $r->print(<<END);
+<p>
+Compiling Excel Workbook with a worksheet for each student.
+</p><p>
+This operation may take longer than a complete recalculation of the
+spreadsheet. 
+</p><p>
+To abort this operation, hit the stop button on your browser.
+</p><p>
+A link to the spreadsheet will be available at the end of this process.
+</p>
+<p>
+END
     $r->rflush();
+    my $starttime = time;
     foreach (keys(%f)) {
 	next if ($_!~/^A(\d+)/ || $1 == 0 || ($f{$_}=~/^[!~-]/));
         $count++;
@@ -1424,32 +1457,37 @@
         # Create a new spreadsheet
         my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef);
         # Read in the spreadsheet definition
-        &readsheet($studentsheet,'default_studentcalc');
-        # Determine the structure (contained assessments, etc) of the sheet
-        &updatesheet($studentsheet);
-        # Load in the (possibly cached) data from the assessment sheets        
-        &loadrows($studentsheet);
-        # Compute the sheet
-        &calcsheet($studentsheet);
-        &Apache::lonnet::logthis("Sheet value for A0 = ".$sheet->{'values'}->{'A0'});
+        &update_student_sheet($studentsheet);
         # Stuff the sheet into excel
         &export_sheet_as_excel($studentsheet,$student_excel_worksheet);
+        my $totaltime = int((time - $starttime) / $count * $sheet->{'maxrow'});
+        my $timeleft = int((time - $starttime) / $count * ($sheet->{'maxrow'} - $count));
         if ($count % 5 == 0) {
-            $r->print($count.' students completed<br />');
+            $r->print($count.' students completed.'.
+                      '  Time remaining: '.$timeleft.' sec. '.
+                      '  Estimated total time: '.$totaltime." sec <br />\n");
             $r->rflush();
         }
+        if(defined($c) && ($c->aborted())) {
+            last;
+        }
     }
     #
-    $r->print('All students spreadsheets completed<br />');
-    $r->rflush();
-    #
-    # &export_sheet_as_excel fills $worksheet with the data from $sheet
-    &export_sheet_as_excel($sheet,$main_worksheet);
-    #
-    $workbook->close();
-    # Okay, the spreadsheet is taken care of, so give the user a link.
-    $r->print('<br /><br />'.
-              '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
+    if(! $c->aborted() ) {
+        $r->print('All students spreadsheets completed!<br />');
+        $r->rflush();
+        #
+        # &export_sheet_as_excel fills $worksheet with the data from $sheet
+        &export_sheet_as_excel($sheet,$main_worksheet);
+        #
+        $workbook->close();
+        # Okay, the spreadsheet is taken care of, so give the user a link.
+        $r->print('<br /><br />'.
+                  '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
+    } else {
+        $workbook->close();  # Not sure how necessary this is.
+        #unlink('/home/httpd'.$filename); # No need to keep this around?
+    }
     return 1;
 }
 
@@ -1514,6 +1552,7 @@
     my $rows_output=0;
     foreach my $rownum (@Rows) {
         my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
+        next if ($rowlabel =~ /^\s*$/);
         my $cols_output = 0;
         my $label = &format_excel_rowlabel($rowlabel);
         $worksheet->write($rows_output,$cols_output++,$label);
@@ -1975,7 +2014,6 @@
     my $chome =$sheet->{'chome'};
     #
     %Section = ();
-
     #
     # Read class list and row labels
     my $classlist = &Apache::loncoursedata::get_classlist();
@@ -2034,29 +2072,80 @@
 }
 
 # ----------------------------------- Update rows for student and assess sheets
-sub updatestudentassesssheet {
+sub get_student_rowlabels {
     my ($sheet) = @_;
     #
-    my %bighash;
+    my %course_db;
     #
     my $stype = $sheet->{'sheettype'};
     my $uname = $sheet->{'uname'};
     my $udom  = $sheet->{'udom'};
+    #
     $sheet->{'rowlabel'} = {};
-    my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$uname.'_'.$udom;
-    if  ($updatedata{$identifier}) {
-        %{$sheet->{'rowlabel'}}=split(/___;___/,$updatedata{$identifier});
+    #
+    my $identifier =$sheet->{'coursefilename'}.'_'.$stype;
+    if  ($rowlabel_cache{$identifier}) {
+        %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier});
+    } else {
+        # Get the data and store it in the cache
+        # Tie hash
+        tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
+            &GDBM_READER(),0640);
+        if (! tied(%course_db)) {
+            return 'Could not access course data';
+        }
+        #
+        my %assesslist;
+        foreach ('Feedback','Evaluation','Tutoring','Discussion') {
+            my $symb = '_'.lc($_);
+            $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,$_));
+        }
+        #
+        while (my ($key,$srcf) = each(%course_db)) {
+            next if ($key !~ /^src_(\d+)\.(\d+)$/);
+            my $mapid = $1;
+            my $resid = $2;
+            my $id   = $mapid.'.'.$resid;
+            if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
+                my $symb=
+                    &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}).
+                        '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
+                $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':'
+                    .$uname.':'.$udom.':'.$course_db{'title_'.$id};
+            }
+        }
+        untie(%course_db);
+        # Store away the data
+        $sheet->{'rowlabel'} = \%assesslist;
+        $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
+    }
+
+}
+
+sub get_assess_rowlabels {
+    my ($sheet) = @_;
+    #
+    my %course_db;
+    #
+    my $stype = $sheet->{'sheettype'};
+    my $uname = $sheet->{'uname'};
+    my $udom  = $sheet->{'udom'};
+    my $usymb = $sheet->{'usymb'};
+    #
+    $sheet->{'rowlabel'} = {};
+    my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb;
+    #
+    if  ($rowlabel_cache{$identifier}) {
+        %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier});
     } else {
+        # Get the data and store it in the cache
         # Tie hash
-        tie(%bighash,'GDBM_File',$sheet->{'coursefilename'}.'.db',
+        tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
             &GDBM_READER(),0640);
-        if (! tied(%bighash)) {
+        if (! tied(%course_db)) {
             return 'Could not access course data';
         }
-        # Get all assessments
         #
-        # parameter_labels is used in the assessment sheets to provide labels
-        # for the parameters.
         my %parameter_labels=
             ('timestamp' => 
                  'parameter:Timestamp of Last Transaction<br>timestamp',
@@ -2066,29 +2155,16 @@
                  'parameter:Number of Tutor Responses<br>tutornumber',
              'totalpoints' =>
                  'parameter:Total Points Granted<br>totalpoints');
-        #
-        # assesslist holds the descriptions of all assessments
-        my %assesslist;
-        foreach ('Feedback','Evaluation','Tutoring','Discussion') {
-            my $symb = '_'.lc($_);
-            $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,$_));
-        }
-        while (($_,undef) = each(%bighash)) {
-            next if ($_!~/^src\_(\d+)\.(\d+)$/);
-            my $mapid=$1;
-            my $resid=$2;
-            my $id=$mapid.'.'.$resid;
-            my $srcf=$bighash{$_};
+        while (my ($key,$srcf) = each(%course_db)) {
+            next if ($key !~ /^src_(\d+)\.(\d+)$/);
+            my $mapid = $1;
+            my $resid = $2;
+            my $id   = $mapid.'.'.$resid;
             if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
-                my $symb=
-                    &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
-                        '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
-                $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':'
-                    .$uname.':'.$udom.':'.$bighash{'title_'.$id};
-                next if ($stype ne 'assesscalc');
-                foreach my $key (split(/\,/,
-                                       &Apache::lonnet::metadata($srcf,'keys')
-                                       )) {
+                # Loop through the metadata for this key
+                my @Metadata = split(/,/,
+                                     &Apache::lonnet::metadata($srcf,'keys'));
+                foreach my $key (@Metadata) {
                     next if ($key !~ /^(stores|parameter)_/);
                     my $display=
                         &Apache::lonnet::metadata($srcf,$key.'.display');
@@ -2100,46 +2176,43 @@
                     $parameter_labels{$key}='parameter:'.$display;
                 } # end of foreach
             }
-        } # end of foreach (keys(%bighash))
-        untie(%bighash);
-        #
-        # %parameter_labels has a list of storage and parameter displays by 
-        # unikey
-        # %assesslist has a list of all resource, by symb
-        #
-        if ($stype eq 'assesscalc') {
-            $sheet->{'rowlabel'} = \%parameter_labels;
-        } elsif ($stype eq 'studentcalc') {
-            $sheet->{'rowlabel'} = \%assesslist;
-        }
-        $updatedata{$sheet->{'coursefilename'}.'_'.$stype.'_'
-                        .$uname.'_'.$udom}=
-                            join('___;___',%{$sheet->{'rowlabel'}});
-        # Get current from cache
+        }
+        untie(%course_db);
+        # Store away the results
+        $sheet->{'rowlabel'} = \%parameter_labels;
+        $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
     }
-    # Find discrepancies between the course row table and this
-    #
+        
+}
+
+sub updatestudentassesssheet {
+    my $sheet = shift;
+    if ($sheet->{'sheettype'} eq 'studentcalc') {
+        &get_student_rowlabels($sheet);
+    } else {
+        &get_assess_rowlabels($sheet);
+    }
+    # Determine if any of the information has changed
     my %f=&getformulas($sheet);
     my $changed=0;
     
     $sheet->{'maxrow'} = 0;
     my %existing=();
     # Now obsolete rows
-    foreach (keys(%f)) {
-        next if ($_!~/^A(\d+)/);
-        if ($1 > $sheet->{'maxrow'}) {
-            $sheet->{'maxrow'} = $1;
-        }
-        my ($usy,$ufn)=split(/__&&&\__/,$f{$_});
+    while (my ($cell, $formula) = each (%f)) {
+        next if ($cell !~ /^A(\d+)/);
+        $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'});
+        my ($usy,$ufn)=split(/__&&&\__/,$formula);
         $existing{$usy}=1;
         unless ((exists($sheet->{'rowlabel'}->{$usy}) && 
                  (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) ||
-                ($f{$_}=~/^(~~~|---)/))){
+                 ($formula =~ /^(~~~|---)/) )) {
             $f{$_}='!!! Obsolete';
             $changed=1;
         } elsif ($ufn) {
+            # I do not think this works any more
             $sheet->{'rowlabel'}->{$usy}
-                =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;
+                =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn&\usymb\=/;
         }
     }
     # New and unknown keys
@@ -2154,8 +2227,6 @@
         $sheet->{'f'} = \%f;
         &setformulas($sheet); 
     }
-    #
-    undef %existing;
 }
 
 # ------------------------------------------------ Load data for one assessment
@@ -2176,18 +2247,18 @@
     undef @tmp;
     # 
     my @assessdata=();
-    foreach (keys(%f)) {
-	next if ($_!~/^A(\d+)/);
+    while (my ($cell,$value) = each (%f)) {
+	next if ($cell !~ /^A(\d+)/);
         my $row=$1;
-        next if (($f{$_}=~/^[\!\~\-]/) || ($row==0));
-        my ($usy,$ufn)=split(/__&&&\__/,$f{$_});
+        next if (($value =~ /^[!~-]/) || ($row==0));
+        my ($usy,$ufn)=split(/__&&&\__/,$value);
         @assessdata=&exportsheet($sheet,$sheet->{'uname'},
                                  $sheet->{'udom'},
                                  'assesscalc',$usy,$ufn);
         my $index=0;
         foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
-            if ($assessdata[$index]) {
+            if (defined($assessdata[$index])) {
                 my $col=$_;
                 if ($assessdata[$index]=~/\D/) {
                     $c{$col.$row}="'".$assessdata[$index]."'";
@@ -2426,8 +2497,7 @@
 
 sub updatesheet {
     my ($sheet)=@_;
-    my $stype=$sheet->{'sheettype'};
-    if ($stype eq 'classcalc') {
+    if ($sheet->{'sheettype'} eq 'classcalc') {
 	return &updateclasssheet($sheet);
     } else {
         return &updatestudentassesssheet($sheet);
@@ -2503,6 +2573,7 @@
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
     my $found='';
     if ($oldsheets{$key}) {
+        &Apache::lonnet::logthis("got cached $stype for $uname");
         foreach (split(/___&\___/,$oldsheets{$key})) {
             my ($name,$value)=split(/___=___/,$_);
             if ($name eq $fn) {
@@ -2513,6 +2584,7 @@
     unless ($found) {
         &cachedssheets($sheet,$uname,$udom);
         if ($oldsheets{$key}) {
+            &Apache::lonnet::logthis("got cached $stype for $uname");
             foreach (split(/___&\___/,$oldsheets{$key})) {
                 my ($name,$value)=split(/___=___/,$_);
                 if ($name eq $fn) {
@@ -2602,7 +2674,7 @@
 #
 # Load previously cached student spreadsheets for this course
 #
-sub expirationdates {
+sub load_spreadsheet_expirationdates {
     undef %expiredates;
     my $cid=$ENV{'request.course.id'};
     my @tmp = &Apache::lonnet::dump('nohist_expirationdates',
@@ -2640,18 +2712,22 @@
     my ($sheet,$uname,$udom) = @_;
     $uname = $uname || $sheet->{'uname'};
     $udom  = $udom  || $sheet->{'udom'};
-    if (! $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}) {
+    if (! $loadedcaches{$uname.'_'.$udom}) {
         my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
                                         $sheet->{'udom'},
                                         $sheet->{'uname'});
         if ($tmp[0] !~ /^error/) {
-            my %StupidTempHash = @tmp;
-            while (my ($key,$value) = each %StupidTempHash) {
+            my %TempHash = @tmp;
+            my $count = 0;
+            while (my ($key,$value) = each %TempHash) {
                 $oldsheets{$key} = $value;
+                $count++;
             }
+            &Apache::lonnet::logthis('saved '.$count.' cached sheets for '.$uname);
             $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1;
         }
     }
+    
 }
 
 # ===================================================== Calculated sheets cache
@@ -2677,6 +2753,9 @@
               lc($ENV{'form.output'}) eq 'recursive excel')) {
         $ENV{'form.output'} = 'HTML';
     }
+    #
+    # Overload checking
+    #
     # Check this server
     my $loaderror=&Apache::lonnet::overloaderror($r);
     if ($loaderror) { return $loaderror; }
@@ -2684,15 +2763,22 @@
     $loaderror= &Apache::lonnet::overloaderror($r,
                       $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
     if ($loaderror) { return $loaderror; } 
-    
+    #
+    # HTML Header
+    #
     if ($r->header_only) {
         $r->content_type('text/html');
         $r->send_http_header;
         return OK;
     }
+    #
     # Global directory configs
+    #
     $includedir = $r->dir_config('lonIncludes');
     $tmpdir = $r->dir_config('lonDaemons').'/tmp/';
+    #
+    # Roles Checking
+    #
     # Needs to be in a course
     if (! $ENV{'request.course.fn'}) { 
         # Not in a course, or not allowed to modify parms
@@ -2700,17 +2786,27 @@
             $r->uri.":opa:0:0:Cannot modify spreadsheet";
         return HTTP_NOT_ACCEPTABLE; 
     }
+    #
     # Get query string for limited number of parameters
+    #
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                             ['uname','udom','usymb','ufn']);
+    #
+    # Deal with restricted student permissions 
+    #
     if ($ENV{'request.role'} =~ /^st\./) {
         delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));
         delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));
     }
+    #
+    # Clean up symb and spreadsheet filename
+    #
     if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) {
         $ENV{'form.ufn'}='default_'.$1;
     }
+    #
     # Interactive loading of specific sheet?
+    #
     if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
         $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
     }
@@ -2726,12 +2822,15 @@
         $adom=$ENV{'form.udom'};
     }
     #
-    # Open page
+    # Open page, try to prevent browser cache.
+    #
     $r->content_type('text/html');
     $r->header_out('Cache-control','no-cache');
     $r->header_out('Pragma','no-cache');
     $r->send_http_header;
-    # Screen output
+    #
+    # Header....
+    #
     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
     if ($ENV{'request.role'} !~ /^st\./) {
         $r->print(<<ENDSCRIPT);
@@ -2771,13 +2870,14 @@
     $r->rflush();
     #
     # Full recalc?
+    #
     if ($ENV{'form.forcerecalc'}) {
         $r->print('<h4>Completely Recalculating Sheet ...</h4>');
         undef %spreadsheets;
         undef %courserdatas;
         undef %userrdatas;
         undef %defaultsheets;
-        undef %updatedata;
+        undef %rowlabel_cache;
     }
     # Read new sheet or modified worksheet
     my ($sheet)=&makenewsheet($aname,$adom,$sheettype,$ENV{'form.usymb'});
@@ -2861,10 +2961,18 @@
                             &othersheets($sheet,'assesscalc'));
         }
     }
-    # Cached sheets
-    &expirationdates();
-    undef %oldsheets;
-    undef %loadedcaches;
+    #
+    # Set up caching mechanisms
+    #
+    &load_spreadsheet_expirationdates();
+    # Clear out old caches if we have not seen this class before.
+    if (exists($oldsheets{'course'}) &&
+        $oldsheets{'course'} ne $sheet->{'cid'}) {
+        undef %oldsheets;
+        undef %loadedcaches;
+    }
+    $oldsheets{'course'} = $sheet->{'cid'};
+    #
     if ($sheet->{'sheettype'} eq 'classcalc') {
         $r->print("Loading previously calculated student sheets ...\n");
         $r->rflush();

--matthew1036769283--