--- loncom/interface/Attic/lonspreadsheet.pm	2001/01/02 12:12:43	1.27
+++ loncom/interface/Attic/lonspreadsheet.pm	2001/03/20 21:34:34	1.48
@@ -3,10 +3,12 @@
 #
 # 11/11,11/15,11/27,12/04,12/05,12/06,12/07,
 # 12/08,12/09,12/11,12/12,12/15,12/16,12/18,12/19,12/30,
-# 01/01/01,02/01 Gerd Kortemeyer
+# 01/01/01,02/01,03/01,19/01,20/01,22/01,
+# 03/05,03/08,03/10,03/12,03/13,03/15,03/17,
+# 03/19,03/20 Gerd Kortemeyer
 
 package Apache::lonspreadsheet;
-
+            
 use strict;
 use Safe;
 use Safe::Hole;
@@ -17,10 +19,30 @@ use GDBM_File;
 use HTML::TokeParser;
 
 #
+# Caches for previously calculated spreadsheets
+#
+
+my %oldsheets;
+my %loadedcaches;
+my %expiredates;
+
+#
+# Cache for stores of an individual user
+#
+
+my $cachedassess;
+my %cachedstores;
+
+#
 # These cache hashes need to be independent of user, resource and course
 # (user and course can/should be in the keys)
 #
-use vars qw(%spreadsheets %courserdatas %userrdatas %defaultsheets);
+
+my %spreadsheets;
+my %courserdatas;
+my %userrdatas;
+my %defaultsheets;
+my %updatedata;
 
 #
 # These global hashes are dependent on user, course and resource, 
@@ -30,11 +52,16 @@ my %courseopt;
 my %useropt;
 my %parmhash;
 
+# Stuff that only the screen handler can know
+
+my $includedir;
+my $tmpdir;
+
 # =============================================================================
 # ===================================== Implements an instance of a spreadsheet
 
 sub initsheet {
-    my $safeeval = new Safe;
+    my $safeeval = new Safe(shift);
     my $safehole = new Safe::Hole;
     $safeeval->permit("entereval");
     $safeeval->permit(":base_math");
@@ -51,11 +78,11 @@ sub initsheet {
 # c: preloaded constants (A-column)
 # rl: row label
 
-%v=(); 
-%t=();
-%f=();
-%c=();
-%rl=();
+undef %v; 
+undef %t;
+undef %f;
+undef %c;
+undef %rl;
 
 $maxrow=0;
 $sheettype='';
@@ -75,6 +102,8 @@ $csec='';
 $chome='';
 $cnum='';
 $cdom='';
+$cid='';
+$cfn='';
 
 # symb
 
@@ -297,6 +326,7 @@ sub sett {
                     $t{$lb}=~s/\#/$trow/g;
                     $t{$lb}=~s/\.\.+/\,/g;
                     $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+                    $t{$lb}=~s/(^|[^\"\'])\[(\w+)\]/$1\$c\{\'$2\'\}/g;
                 }
 	      }
             } keys %f;
@@ -305,7 +335,8 @@ sub sett {
     } keys %f;
     map {
 	if (($f{$_}) && ($_!~/template\_/)) {
-            if ($_=~/^$pattern/) {
+            my $matches=($_=~/^$pattern(\d+)/);
+            if  (($matches) && ($1)) {
 	        unless ($f{$_}=~/^\!/) {
 		    $t{$_}=$c{$_};
                 }
@@ -313,12 +344,14 @@ sub sett {
 	       $t{$_}=$f{$_};
                $t{$_}=~s/\.\.+/\,/g;
                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+               $t{$_}=~s/(^|[^\"\'])\[([\w\.]+)\]/$1\$c\{\'$2\'\}/g;
             }
         }
     } keys %f;
     $t{'A0'}=$f{'A0'};
     $t{'A0'}=~s/\.\.+/\,/g;
     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+    $t{'A0'}=~s/(^|[^\"\'])\[([\w\.]+)\]/$1\$c\{\'$2\'\}/g;
 }
 
 sub calc {
@@ -399,13 +432,12 @@ sub outrow {
 }
 
 sub exportrowa {
-    my $rowa='';
+    my @exportarray=();
     map {
-	$rowa.=$v{$_.'0'}."___;___";
+	$exportarray[$#exportarray+1]=$v{$_.'0'};
     } ('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');
-    $rowa=~s/\_\_\_\;\_\_\_$//;
-    return $rowa;
+    return @exportarray;
 }
 
 # ------------------------------------------- End of "Inside of the safe space"
@@ -417,22 +449,22 @@ ENDDEFS
 # ------------------------------------------------ Add or change formula values
 
 sub setformulas {
-    my ($safeeval,@f)=@_;
-    $safeeval->reval('%f='."('".join("','",@f)."');");
+    my ($safeeval,%f)=@_;
+    %{$safeeval->varglob('f')}=%f;
 }
 
 # ------------------------------------------------ Add or change formula values
 
 sub setconstants {
-    my ($safeeval,@c)=@_;
-    $safeeval->reval('%c='."('".join("','",@c)."');");
+    my ($safeeval,%c)=@_;
+    %{$safeeval->varglob('c')}=%c;
 }
 
 # ------------------------------------------------ Add or change formula values
 
 sub setrowlabels {
-    my ($safeeval,@rl)=@_;
-    $safeeval->reval('%rl='."('".join("','",@rl)."');");
+    my ($safeeval,%rl)=@_;
+    %{$safeeval->varglob('rl')}=%rl;
 }
 
 # ------------------------------------------------------- Calculate spreadsheet
@@ -453,14 +485,7 @@ sub getvalues {
 
 sub getformulas {
     my $safeeval=shift;
-    return $safeeval->reval('%f');
-}
-
-# -------------------------------------------------------------------- Set type
-
-sub settype {
-    my ($safeeval,$type)=@_;
-    $safeeval->reval('$sheettype="'.$type.'";');
+    return %{$safeeval->varglob('f')};
 }
 
 # -------------------------------------------------------------------- Get type
@@ -498,6 +523,20 @@ sub getfilename {
     return $safeeval->reval('$filename');
 }
 
+# --------------------------------------------------------------- Get course ID
+
+sub getcid {
+    my $safeeval=shift;
+    return $safeeval->reval('$cid');
+}
+
+# --------------------------------------------------------- Get course filename
+
+sub getcfn {
+    my $safeeval=shift;
+    return $safeeval->reval('$cfn');
+}
+
 # ----------------------------------------------------------- Get course number
 
 sub getcnum {
@@ -556,7 +595,7 @@ sub getusymb {
 
 # ------------------------------------------------------------- Export of A-row
 
-sub exportrow {
+sub exportdata {
     my $safeeval=shift;
     return $safeeval->reval('&exportrowa()');
 }
@@ -684,20 +723,20 @@ sub readsheet {
   my ($safeeval,$fn)=@_;
   my $stype=&gettype($safeeval);
   my $cnum=&getcnum($safeeval);
+  my $cdom=&getcdom($safeeval);
+  my $chome=&getchome($safeeval);
 
 # --------- There is no filename. Look for defaults in course and global, cache
 
   unless($fn) {
-      unless ($fn=$defaultsheets{$cnum.'_'.$stype}) {
-         $fn=&Apache::lonnet::reply('get:'.
-                $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-                $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.
-                ':environment:spreadsheet_default_'.&gettype($safeeval),
-                     $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
+      unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {
+         $fn=&Apache::lonnet::reply('get:'.$cdom.':'.$cnum.
+                                    ':environment:spreadsheet_default_'.$stype,
+                                    $chome);
          unless (($fn) && ($fn!~/^error\:/)) {
 	     $fn='default_'.$stype;
          }
-         $defaultsheets{$cnum.'_'.$stype}=$fn; 
+         $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; 
       }
   }
 
@@ -707,8 +746,8 @@ sub readsheet {
 
 # ------------------------------------------------------ see if sheet is cached
   my $fstring='';
-  if ($fstring=$spreadsheets{$cnum.'_'.$stype.'_'.$fn}) {
-      &setformulas($sheetone,split(/\_\_\_\;\_\_\_/,$fstring));
+  if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {
+      &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring));
   } else {
 
 # ---------------------------------------------------- Not cached, need to read
@@ -719,7 +758,7 @@ sub readsheet {
 	my $sheetxml='';
        {
          my $fh;
-         if ($fh=Apache::File->new($r->dir_config('lonIncludes').
+         if ($fh=Apache::File->new($includedir.
                          '/default.'.&gettype($safeeval))) {
                $sheetxml=join('',<$fh>);
           }
@@ -739,11 +778,9 @@ sub readsheet {
           }
         }
       } else {
-        my $sheet='';
-        my $reply=&Apache::lonnet::reply('dump:'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.$fn,
-              $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
+          my $sheet='';
+          my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.$fn,
+                                         $chome);
           unless ($reply=~/^error\:/) {
              $sheet=$reply;
 	  }
@@ -754,37 +791,73 @@ sub readsheet {
           } split(/\&/,$sheet);
        }
 # --------------------------------------------------------------- Cache and set
-       $spreadsheets{$cnum.'_'.$stype.'_'.$fn}=join('___;___',%f);       
+       $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);  
        &setformulas($safeeval,%f);
     }
 }
 
+# -------------------------------------------------------- Make new spreadsheet
+
+sub makenewsheet {
+    my ($uname,$udom,$stype,$usymb)=@_;
+    my $safeeval=initsheet($stype);
+    $safeeval->reval(
+       '$uname="'.$uname.
+      '";$udom="'.$udom.
+      '";$uhome="'.&Apache::lonnet::homeserver($uname,$udom).
+      '";$sheettype="'.$stype.
+      '";$usymb="'.$usymb.
+      '";$csec="'.&Apache::lonnet::usection($udom,$uname,
+                                            $ENV{'request.course.id'}).
+      '";$cid="'.$ENV{'request.course.id'}.
+      '";$cfn="'.$ENV{'request.course.fn'}.
+      '";$cnum="'.$ENV{'course.'.$ENV{'request.course.id'}.'.num'}.
+      '";$cdom="'.$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.
+      '";$chome="'.$ENV{'course.'.$ENV{'request.course.id'}.'.home'}.'";');
+    return $safeeval;
+}
+
 # ------------------------------------------------------------ Save spreadsheet
 
 sub writesheet {
-  my $safeeval=shift;
-  if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
+  my ($safeeval,$makedef)=@_;
+  my $cid=&getcid($safeeval);
+  if (&Apache::lonnet::allowed('opa',$cid)) {
     my %f=&getformulas($safeeval);
+    my $stype=&gettype($safeeval);
+    my $cnum=&getcnum($safeeval);
+    my $cdom=&getcdom($safeeval);
+    my $chome=&getchome($safeeval);
+    my $fn=&getfilename($safeeval);
+
+# ------------------------------------------------------------- Cache new sheet
+    $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);    
+# ----------------------------------------------------------------- Write sheet
     my $sheetdata='';
     map {
        $sheetdata.=&Apache::lonnet::escape($_).'='.
 	   &Apache::lonnet::escape($f{$_}).'&';
     } keys %f;
     $sheetdata=~s/\&$//;
-    my $reply=&Apache::lonnet::reply('put:'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.
-              &getfilename($safeeval).':'.
-              $sheetdata,
-              $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
+    my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'.
+              $sheetdata,$chome);
     if ($reply eq 'ok') {
-          return &Apache::lonnet::reply('put:'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.
-              &gettype($safeeval).'_spreadsheets:'.
-              &Apache::lonnet::escape(&getfilename($safeeval)).'='.
-              $ENV{'user.name'},
-              $ENV{'course.'.$ENV{'request.course.id'}.'.home'});    
+          $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.
+              $stype.'_spreadsheets:'.
+              &Apache::lonnet::escape($fn).'='.$ENV{'user.name'},
+              $chome);
+          if ($reply eq 'ok') {
+              if ($makedef) { 
+                return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum.
+                                ':environment:spreadsheet_default_'.$stype.'='.
+                                &Apache::lonnet::escape($fn),
+                                $chome);
+	      } else {
+		  return $reply;
+    	      }
+	   } else {
+	       return $reply;
+           }
       } else {
 	  return $reply;
       }
@@ -793,10 +866,14 @@ sub writesheet {
 }
 
 # ----------------------------------------------- Make a temp copy of the sheet
+# "Modified workcopy" - interactive only
+#
 
 sub tmpwrite {
-    my ($safeeval,$tmpdir,$symb)=@_;
-    my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval);
+    my $safeeval=shift;
+    my $fn=$ENV{'user.name'}.'_'.
+           $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.
+           &getfilename($safeeval);
     $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';
     my $fh;
@@ -808,8 +885,10 @@ sub tmpwrite {
 # ---------------------------------------------------------- Read the temp copy
 
 sub tmpread {
-    my ($safeeval,$tmpdir,$symb,$nfield,$nform)=@_;
-    my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval);
+    my ($safeeval,$nfield,$nform)=@_;
+    my $fn=$ENV{'user.name'}.'_'.
+           $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.
+           &getfilename($safeeval);
     $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';
     my $fh;
@@ -827,38 +906,22 @@ sub tmpread {
     &setformulas($safeeval,%fo);
 }
 
-# --------------------------------------------------------------- Read metadata
-
-sub readmeta {
-    my $fn=shift;
-    unless ($fn=~/\.meta$/) { $fn.='meta'; }
-    my $content;
-    my %returnhash=();
-    {
-      my $fh=Apache::File->new($fn);
-      $content=join('',<$fh>);
-    }
-   my $parser=HTML::TokeParser->new(\$content);
-   my $token;
-   while ($token=$parser->get_token) {
-      if ($token->[0] eq 'S') {
-         my $entry=$token->[1];
-         if (($entry eq 'stores') || ($entry eq 'parameter')) {
-             my $unikey=$entry;
-             $unikey.='_'.$token->[2]->{'part'}; 
-             $unikey.='_'.$token->[2]->{'name'}; 
-             $returnhash{$unikey}=$token->[2]->{'display'};
-         }
-     }
-  }
-    return %returnhash;
-}
-
 # ================================================================== Parameters
 # -------------------------------------------- Figure out a cascading parameter
+#
+# For this function to work
+#
+# * parmhash needs to be tied
+# * courseopt and useropt need to be initialized for this user and course
+#
 
 sub parmval {
-    my ($what,$symb)=@_;
+    my ($what,$safeeval)=@_;
+    my $cid=&getcid($safeeval);
+    my $csec=&getcsec($safeeval);
+    my $uname=&getuname($safeeval);
+    my $udom=&getudom($safeeval);
+    my $symb=&getusymb($safeeval);
 
     unless ($symb) { return ''; }
     my $result='';
@@ -871,23 +934,24 @@ sub parmval {
 
        my $symbparm=$symb.'.'.$what;
        my $mapparm=$mapname.'___(all).'.$what;
+       my $usercourseprefix=$uname.'_'.$udom.'_'.$cid;
 
        my $seclevel=
-            $ENV{'request.course.id'}.'.['.
+            $usercourseprefix.'.['.
 		$csec.'].'.$what;
        my $seclevelr=
-            $ENV{'request.course.id'}.'.['.
+            $usercourseprefix.'.['.
 		$csec.'].'.$symbparm;
        my $seclevelm=
-            $ENV{'request.course.id'}.'.['.
+            $usercourseprefix.'.['.
 		$csec.'].'.$mapparm;
 
        my $courselevel=
-            $ENV{'request.course.id'}.'.'.$what;
+            $usercourseprefix.'.'.$what;
        my $courselevelr=
-            $ENV{'request.course.id'}.'.'.$symbparm;
+            $usercourseprefix.'.'.$symbparm;
        my $courselevelm=
-            $ENV{'request.course.id'}.'.'.$mapparm;
+            $usercourseprefix.'.'.$mapparm;
 
 # ---------------------------------------------------------- fourth, check user
       
@@ -932,13 +996,17 @@ sub parmval {
 
 # ---------------------------------------------- Update rows for course listing
 
-sub updatestudentrows {
+sub updateclasssheet {
     my $safeeval=shift;
-    my $cid=$ENV{'request.course.id'};
+    my $cnum=&getcnum($safeeval);
+    my $cdom=&getcdom($safeeval);
+    my $cid=&getcid($safeeval);
+    my $chome=&getchome($safeeval);
+
+# ---------------------------------------------- Read class list and row labels
+
     my $classlst=&Apache::lonnet::reply
-                 ('dump:'.$ENV{'course.'.$cid.'.domain'}.':'.
-	                  $ENV{'course.'.$cid.'.num'}.':classlist',
-	                  $ENV{'course.'.$cid.'.home'});
+                                 ('dump:'.$cdom.':'.$cnum.':classlist',$chome);
     my %currentlist=();
     my $now=time;
     unless ($classlst=~/^error\:/) {
@@ -950,23 +1018,24 @@ sub updatestudentrows {
             if ($active) {
                 my $rowlabel='';
                 $name=&Apache::lonnet::unescape($name);
-                my ($cname,$cdom)=split(/\:/,$name);
-                my $csec=
-             &Apache::lonnet::usection($cdom,$cname,$ENV{'request.course.id'});
-                if ($csec==-1) {
+                my ($sname,$sdom)=split(/\:/,$name);
+                my $ssec=&Apache::lonnet::usection($sdom,$sname,$cid);
+                if ($ssec==-1) {
                     $rowlabel='<font color=red>Data not available: '.$name.
 			      '</font>';
                 } else {
-                    my %reply=&Apache::lonnet::idrget($cdom,$cname);
-                    my $reply=&Apache::lonnet::reply('get:'.$cdom.':'.$cname.
+                    my %reply=&Apache::lonnet::idrget($sdom,$sname);
+                    my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname.
 		      ':environment:firstname&middlename&lastname&generation',
-                      &Apache::lonnet::homeserver($cname,$cdom));
-                    $rowlabel=$csec.'&nbsp;'.$reply{$cname}.'<br>';
+                      &Apache::lonnet::homeserver($sname,$sdom));
+                    $rowlabel='<a href="/adm/studentcalc?uname='.$sname.
+                              '&udom='.$sdom.'">'.
+                              $ssec.'&nbsp;'.$reply{$sname}.'<br>';
                     map {
                         $rowlabel.=&Apache::lonnet::unescape($_).' ';
                     } split(/\&/,$reply);
+                    $rowlabel.='</a>';
                 }
-               
 		$currentlist{&Apache::lonnet::unescape($name)}=$rowlabel;
             }
         } split(/\&/,$classlst);
@@ -1010,11 +1079,15 @@ sub updatestudentrows {
         return 'Could not access course data';
     }
 }
-# ----------------------------------------------------------------- Update rows
 
-sub updaterows {
+# ----------------------------------- Update rows for student and assess sheets
+
+sub updatestudentassesssheet {
     my $safeeval=shift;
     my %bighash;
+    my $stype=&gettype($safeeval);
+    my %current=();
+    unless ($updatedata{$ENV{'request.course.fn'}.'_'.$stype}) {
 # -------------------------------------------------------------------- Tie hash
       if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',
                        &GDBM_READER,0640)) {
@@ -1023,8 +1096,6 @@ sub updaterows {
 	my %allkeys=();
         my %allassess=();
 
-        my $stype=&gettype($safeeval);
-
         map {
 	    if ($_=~/^src\_(\d+)\.(\d+)$/) {
 	       my $mapid=$1;
@@ -1036,8 +1107,8 @@ sub updaterows {
                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
 			    '___'.$resid.'___'.
 			    &Apache::lonnet::declutter($srcf);
-		 $allassess{$symb}=$bighash{'title_'.$id};
-
+		 $allassess{$symb}=
+    '<a href="/adm/assesscalc?usymb='.$symb.'">'.$bighash{'title_'.$id}.'</a>';
                  if ($stype eq 'assesscalc') {
                    map {
                        if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) {
@@ -1045,9 +1116,10 @@ sub updaterows {
                           my $display=
 			      &Apache::lonnet::metadata($srcf,$key.'.display');
                           unless ($display) {
-                              $display=
+                              $display.=
 			         &Apache::lonnet::metadata($srcf,$key.'.name');
                           }
+                          $display.='<br>'.$key;
                           $allkeys{$key}=$display;
 		       }
                    } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));
@@ -1061,17 +1133,26 @@ sub updaterows {
 # %allkeys has a list of storage and parameter displays by unikey
 # %allassess has a list of all resource displays by symb
 #
-# -------------------- Find discrepancies between the course row table and this
-#
-        my %f=&getformulas($safeeval);
-        my $changed=0;
 
-        my %current=();
         if ($stype eq 'assesscalc') {
 	    %current=%allkeys;
         } elsif ($stype eq 'studentcalc') {
             %current=%allassess;
         }
+        $updatedata{$ENV{'request.course.fn'}.'_'.$stype}=
+	    join('___;___',%current);
+    } else {
+        return 'Could not access course data';
+    }
+# ------------------------------------------------------ Get current from cache
+    } else {
+        %current=split(/\_\_\_\;\_\_\_/,
+		       $updatedata{$ENV{'request.course.fn'}.'_'.$stype});
+    }
+# -------------------- Find discrepancies between the course row table and this
+#
+        my %f=&getformulas($safeeval);
+        my $changed=0;
 
         my $maxrow=0;
         my %existing=();
@@ -1097,39 +1178,56 @@ sub updaterows {
                 $f{'A'.$maxrow}=$_;
             }
         } keys %current;        
-     
+    
         if ($changed) { &setformulas($safeeval,%f); }
 
         &setmaxrow($safeeval,$maxrow);
         &setrowlabels($safeeval,%current);
-
-    } else {
-        return 'Could not access course data';
-    }
+ 
+        undef %current;
+        undef %existing;
 }
 
 # ------------------------------------------------ Load data for one assessment
 
-sub rowazstudent {
+sub loadstudent {
     my $safeeval=shift;
     my %c=();
     my %f=&getformulas($safeeval);
+    $cachedassess=&getuname($safeeval).':'.&getudom($safeeval);
+    %cachedstores=();
+    {
+      my $reply=&Apache::lonnet::reply('dump:'.&getudom($safeeval).':'.
+                                               &getuname($safeeval).':'.
+                                               &getcid($safeeval),
+                                               &getuhome($safeeval));
+      unless ($reply=~/^error\:/) {
+         map {
+            my ($name,$value)=split(/\=/,$_);
+            $cachedstores{&Apache::lonnet::unescape($name)}=
+	                  &Apache::lonnet::unescape($value);
+         } split(/\&/,$reply);
+      }
+    }
+    my @assessdata=();
     map {
 	if ($_=~/^A(\d+)/) {
 	   my $row=$1;
-           unless ($f{$_}=~/^\!/) {
-              my @assessdata=split(/\_\_\_\;\_\_\_/,
-                             &Apache::lonnet::ssi(
-                       '/adm/assesscalc',('utarget' => 'export',
-                                          'uname'   => $uname,
-                                          'udom'    => $udom,
-			                  'usymb'   => $f{$_})));
+           unless (($f{$_}=~/^\!/) || ($row==0)) {
+	      @assessdata=&exportsheet(&getuname($safeeval),
+                                       &getudom($safeeval),
+                                       'assesscalc',$f{$_});
               my $index=0;
               map {
                   if ($assessdata[$index]) {
-		     $c{$_.$row}=$assessdata[$index];
-                     unless ($_ eq 'A') { 
-			 $f{$_.$row}='import';
+		     my $col=$_;
+		     if ($assessdata[$index]=~/\D/) {
+                         $c{$col.$row}="'".$assessdata[$index]."'";
+ 		     } else {
+		         $c{$col.$row}=$assessdata[$index];
+		     }
+                     unless ($col eq 'A') { 
+			 $f{$col.$row}='import';
                      }
 		  }
                   $index++;
@@ -1138,32 +1236,62 @@ sub rowazstudent {
 	   }
         }
     } keys %f;
+    $cachedassess='';
+    undef %cachedstores;
     &setformulas($safeeval,%f);
     &setconstants($safeeval,%c);
 }
 
 # --------------------------------------------------- Load data for one student
 
-sub rowazclass {
-    my $safeeval=shift;
+sub loadcourse {
+    my ($safeeval,$r)=@_;
     my %c=();
     my %f=&getformulas($safeeval);
+    my $total=0;
+    map {
+	if ($_=~/^A(\d+)/) {
+	    unless ($f{$_}=~/^\!/) { $total++; }
+        }
+    } keys %f;
+    my $now=0;
+    my $since=time;
+    $r->print(<<ENDPOP);
+<script>
+    popwin=open('','popwin','width=400,height=100');
+    popwin.document.writeln('<html><body bgcolor="#FFFFFF">'+
+      '<h1>Spreadsheet Calculation Progress</h1>'+
+      '<form name=popremain>'+
+      '<input type=text size=35 name=remaining value=Starting></form>'+
+      '</body></html>');
+    popwin.document.close();
+</script>
+ENDPOP
+    $r->rflush();
     map {
 	if ($_=~/^A(\d+)/) {
 	   my $row=$1;
-           unless ($f{$_}=~/^\!/) {
-	      my ($tname,$tdom)=split(/\:/,$_);
-              my @assessdata=split(/\_\_\_\;\_\_\_/,
-                             &Apache::lonnet::ssi(
-                      '/adm/studentcalc',('utarget' => 'export',
-                                          'uname'   => $tname,
-                                          'udom'    => $tdom)));
+           unless (($f{$_}=~/^\!/)  || ($row==0)) {
+	      my @studentdata=&exportsheet(split(/\:/,$f{$_}),
+                                           'studentcalc');
+              undef %userrdatas;
+              $now++;
+              $r->print('<script>popwin.document.popremain.remaining.value="'.
+                  $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).
+                        ' secs remaining";</script>');
+              $r->rflush(); 
+
               my $index=0;
               map {
-                  if ($assessdata[$index]) {
-		     $c{$_.$row}=$assessdata[$index];
-                     unless ($_ eq 'A') { 
-			 $f{$_.$row}='import';
+                  if ($studentdata[$index]) {
+		     my $col=$_;
+		     if ($studentdata[$index]=~/\D/) {
+                         $c{$col.$row}="'".$studentdata[$index]."'";
+ 		     } else {
+		         $c{$col.$row}=$studentdata[$index];
+		     }
+                     unless ($col eq 'A') { 
+			 $f{$col.$row}='import';
                      }
 		  }
                   $index++;
@@ -1174,22 +1302,53 @@ sub rowazclass {
     } keys %f;
     &setformulas($safeeval,%f);
     &setconstants($safeeval,%c);
+    $r->print('<script>popwin.close()</script>');
+    $r->rflush(); 
 }
 
 # ------------------------------------------------ Load data for one assessment
 
-sub rowaassess {
-    my ($safeeval,$symb)=@_;
-    my $uhome=&Apache::lonnet::homeserver($uname,$udom);
+sub loadassessment {
+    my $safeeval=shift;
+
+    my $uhome=&getuhome($safeeval);
+    my $uname=&getuname($safeeval);
+    my $udom=&getudom($safeeval);
+    my $symb=&getusymb($safeeval);
+    my $cid=&getcid($safeeval);
+    my $cnum=&getcnum($safeeval);
+    my $cdom=&getcdom($safeeval);
+    my $chome=&getchome($safeeval);
+
     my $namespace;
-    unless ($namespace=$ENV{'request.course.id'}) { return ''; }
+    unless ($namespace=$cid) { return ''; }
 
 # ----------------------------------------------------------- Get stored values
+
+   my %returnhash=();
+
+   if ($cachedassess eq $uname.':'.$udom) {
+#
+# get data out of the dumped stores
+# 
+
+       my $version=$cachedstores{'version:'.$symb};
+       my $scope;
+       for ($scope=1;$scope<=$version;$scope++) {
+           map {
+               $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};
+           } split(/\:/,$cachedstores{$scope.':keys:'.$symb}); 
+       }
+
+   } else {
+#
+# restore individual
+#
+
     my $answer=&Apache::lonnet::reply(
        "restore:$udom:$uname:".
        &Apache::lonnet::escape($namespace).":".
        &Apache::lonnet::escape($symb),$uhome);
-    my %returnhash=();
     map {
 	my ($name,$value)=split(/\=/,$_);
         $returnhash{&Apache::lonnet::unescape($name)}=
@@ -1201,33 +1360,34 @@ sub rowaassess {
           $returnhash{$_}=$returnhash{$version.':'.$_};
        } split(/\:/,$returnhash{$version.':keys'});
     }
+   }
 # ----------------------------- returnhash now has all stores for this resource
 
 # ---------------------------- initialize coursedata and userdata for this user
-    %courseopt=();
-    %useropt=();
-    my $uhome=&Apache::lonnet::homeserver($uname,$udom);
+    undef %courseopt;
+    undef %useropt;
+
+    my $userprefix=$uname.'_'.$udom.'_';
+
     unless ($uhome eq 'no_host') { 
 # -------------------------------------------------------------- Get coursedata
       unless
-        ((time-$courserdatas{$ENV{'request.course.id'}.'.last_cache'})<120) {
-         my $reply=&Apache::lonnet::reply('dump:'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-              $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':resourcedata',
-              $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
+        ((time-$courserdatas{$cid.'.last_cache'})<240) {
+         my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.
+              ':resourcedata',$chome);
          if ($reply!~/^error\:/) {
-            $courserdatas{$ENV{'request.course.id'}}=$reply;
-            $courserdatas{$ENV{'request.course.id'}.'.last_cache'}=time;
+            $courserdatas{$cid}=$reply;
+            $courserdatas{$cid.'.last_cache'}=time;
          }
       }
       map {
          my ($name,$value)=split(/\=/,$_);
-         $courseopt{&Apache::lonnet::unescape($name)}=
+         $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=
                     &Apache::lonnet::unescape($value);  
-      } split(/\&/,$courserdatas{$ENV{'request.course.id'}});
+      } split(/\&/,$courserdatas{$cid});
 # --------------------------------------------------- Get userdata (if present)
       unless
-        ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<120) {
+        ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {
          my $reply=
        &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);
          if ($reply!~/^error\:/) {
@@ -1237,30 +1397,39 @@ sub rowaassess {
       }
       map {
          my ($name,$value)=split(/\=/,$_);
-         $useropt{&Apache::lonnet::unescape($name)}=
+         $useropt{$userprefix.&Apache::lonnet::unescape($name)}=
 	          &Apache::lonnet::unescape($value);
       } split(/\&/,$userrdatas{$uname.'___'.$udom});
-   }
-# -- now courseopt, useropt initialized for this user and course (used parmval)
+    }
+# ----------------- now courseopt, useropt initialized for this user and course
+# (used by parmval)
 
-    my %c=();
+   my %c=();
+
+   if (tie(%parmhash,'GDBM_File',
+           &getcfn($safeeval).'_parms.db',&GDBM_READER,0640)) {
     my %f=&getformulas($safeeval);
     map {
 	if ($_=~/^A/) {
             unless ($f{$_}=~/^\!/) {
   	       if ($f{$_}=~/^parameter/) {
-	          $c{$_}=&parmval($f{$_},$symb);
+                  my $val=&parmval($f{$_},$safeeval);
+                  $c{$_}=$val;
+                  $c{$f{$_}}=$val;
 	       } else {
 		  my $key=$f{$_};
+                  my $ckey=$key;
                   $key=~s/^stores\_/resource\./;
                   $key=~s/\_/\./;
  	          $c{$_}=$returnhash{$key};
+                  $c{$ckey}=$returnhash{$key};
 	       }
 	   }
         }
     } keys %f;
-
-    &setconstants($safeeval,%c);
+    untie(%parmhash);
+   }
+   &setconstants($safeeval,%c);
 }
 
 # --------------------------------------------------------- Various form fields
@@ -1287,25 +1456,283 @@ sub selectbox {
     return $selout.'</select>';
 }
 
+# =============================================== Update information in a sheet
+#
+# Add new users or assessments, etc.
+#
+
+sub updatesheet {
+    my $safeeval=shift;
+    my $stype=&gettype($safeeval);
+    if ($stype eq 'classcalc') {
+	return &updateclasssheet($safeeval);
+    } else {
+        return &updatestudentassesssheet($safeeval);
+    }
+}
+
+# =================================================== Load the rows for a sheet
+#
+# Import the data for rows
+#
+
+sub loadrows {
+    my ($safeeval,$r)=@_;
+    my $stype=&gettype($safeeval);
+    if ($stype eq 'classcalc') {
+	&loadcourse($safeeval,$r);
+    } elsif ($stype eq 'studentcalc') {
+        &loadstudent($safeeval);
+    } else {
+        &loadassessment($safeeval);
+    }
+}
+
+# ======================================================= Forced recalculation?
+
+sub checkthis {
+    my ($keyname,$time)=@_;
+    return ($time<$expiredates{$keyname});
+}
+sub forcedrecalc {
+    my ($uname,$udom,$stype,$usymb)=@_;
+    my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
+    my $time=$oldsheets{$key.'.time'};
+    unless ($time) { return 1; }
+    if ($stype eq 'assesscalc') {
+        my $map=(split(/\_\_\_/,$usymb))[0];
+        if (&checkthis('::assesscalc:',$time) ||
+            &checkthis('::assesscalc:'.$map,$time) ||
+            &checkthis('::assesscalc:'.$usymb,$time) ||
+            &checkthis($uname.':'.$udom.':assesscalc:',$time)) {
+            return 1;
+        } 
+    } else {
+        if (&checkthis('::studentcalc:',$time) || 
+            &checkthis($uname.':'.$udom.':studencalc:',$time)) {
+	    return 1;
+        }
+    }
+    return 0; 
+}
+
+# ============================================================== Export handler
+#
+# Non-interactive call from with program
+#
+
+sub exportsheet {
+ my ($uname,$udom,$stype,$usymb,$fn)=@_;
+ my @exportarr=();
+#
+# Check if cached
+#
+
+ my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
+ my $found='';
+
+ if ($oldsheets{$key}) {
+     map {
+         my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+         if ($name eq $fn) {
+	     $found=$value;
+         }
+     } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});
+ }
+
+ unless ($found) {
+     &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));
+     if ($oldsheets{$key}) {
+        map {
+            my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+            if ($name eq $fn) {
+	        $found=$value;
+            }
+        } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});
+     }
+ }
+#
+# Check if still valid
+#
+ if ($found) {
+     if (&forcedrecalc($uname,$udom,$stype,$usymb)) {
+	 $found='';
+     }
+ }
+ 
+ if ($found) {
+#
+# Return what was cached
+#
+     @exportarr=split(/\_\_\_\;\_\_\_/,$found);
+
+ } else {
+#
+# Not cached
+#        
+
+    my $thissheet=&makenewsheet($uname,$udom,$stype,$usymb);
+    &readsheet($thissheet,$fn);
+    &updatesheet($thissheet);
+    &loadrows($thissheet);
+    &calcsheet($thissheet); 
+    @exportarr=&exportdata($thissheet);
+#
+# Store now
+#
+    my $cid=$ENV{'request.course.id'}; 
+    my $current='';
+    if ($stype eq 'studentcalc') {
+       $current=&Apache::lonnet::reply('get:'.
+                                     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_calculatedsheets:'.
+                                     &Apache::lonnet::escape($key),
+                                     $ENV{'course.'.$cid.'.home'});
+    } else {
+       $current=&Apache::lonnet::reply('get:'.
+                                     &getudom($thissheet).':'.
+                                     &getuname($thissheet).
+				     ':nohist_calculatedsheets_'.
+                                     $ENV{'request.course.id'}.':'.
+                                     &Apache::lonnet::escape($key),
+                                     &getuhome($thissheet));
+
+    }
+    my %currentlystored=();
+    unless ($current=~/^error\:/) {
+       map {
+           my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+           $currentlystored{$name}=$value;
+       } split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current));
+    }
+    $currentlystored{$fn}=join('___;___',@exportarr);
+
+    my $newstore='';
+    map {
+        if ($newstore) { $newstore.='___&___'; }
+        $newstore.=$_.'___=___'.$currentlystored{$_};
+    } keys %currentlystored;
+    my $now=time;
+    if ($stype eq 'studentcalc') {
+       &Apache::lonnet::reply('put:'.
+                         $ENV{'course.'.$cid.'.domain'}.':'.
+                         $ENV{'course.'.$cid.'.num'}.
+			 ':nohist_calculatedsheets:'.
+                         &Apache::lonnet::escape($key).'='.
+			 &Apache::lonnet::escape($newstore).'&'.
+                         &Apache::lonnet::escape($key).'.time='.$now,
+                         $ENV{'course.'.$cid.'.home'});
+   } else {
+       &Apache::lonnet::reply('put:'.
+                         &getudom($thissheet).':'.
+                         &getuname($thissheet).
+			 ':nohist_calculatedsheets_'.
+                         $ENV{'request.course.id'}.':'.
+                         &Apache::lonnet::escape($key).'='.
+			 &Apache::lonnet::escape($newstore).'&'.
+                         &Apache::lonnet::escape($key).'.time='.$now,
+                         &getuhome($thissheet));
+   }
+ }
+ return @exportarr;
+}
+# ============================================================ Expiration Dates
+#
+# Load previously cached student spreadsheets for this course
+#
+
+sub expirationdates {
+    undef %expiredates;
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.
+				     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_expirationdates',
+                                     $ENV{'course.'.$cid.'.home'});
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $expiredates{&Apache::lonnet::unescape($name)}
+                        =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
+}
+
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached student spreadsheets for this course
+#
+
+sub cachedcsheets {
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.
+				     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_calculatedsheets',
+                                     $ENV{'course.'.$cid.'.home'});
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $oldsheets{&Apache::lonnet::unescape($name)}
+                      =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
+}
+
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached assessment spreadsheets for this student
+#
+
+sub cachedssheets {
+  my ($sname,$sdom,$shome)=@_;
+  unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) {
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname.
+			             ':nohist_calculatedsheets_'.
+                                      $ENV{'request.course.id'},
+                                     $shome);
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $oldsheets{&Apache::lonnet::unescape($name)}
+                      =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
+    $loadedcaches{$sname.'_'.$sdom}=1;
+  }
+}
+
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached assessment spreadsheets for this student
+#
+
 # ================================================================ Main handler
+#
+# Interactive call to screen
+#
+#
+
 
 sub handler {
     my $r=shift;
 
-    $uname='';
-    $udom='';
-    $csec='';
-
-   if ($r->header_only) {
+    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/';
 
 # ----------------------------------------------------- Needs to be in a course
 
-  if (($ENV{'request.course.fn'}) || 
-      ($ENV{'request.state'} eq 'construct')) { 
+  if ($ENV{'request.course.fn'}) { 
 
 # --------------------------- Get query string for limited number of parameters
 
@@ -1322,16 +1749,17 @@ sub handler {
     } (split(/&/,$ENV{'QUERY_STRING'}));
 
 # ------------------------------------------- Nothing there? Must be login user
+
+    my $aname;
+    my $adom;
+
     unless ($ENV{'form.uname'}) {
-	$uname=$ENV{'user.name'};
-        $udom=$ENV{'user.domain'};
+	$aname=$ENV{'user.name'};
+        $adom=$ENV{'user.domain'};
     } else {
-        $uname=$ENV{'form.uname'};
-        $udom=$ENV{'form.udom'};
+        $aname=$ENV{'form.uname'};
+        $adom=$ENV{'form.udom'};
     }
-# ----------------------------------------------------------- Change of target?
-
-    my $reroute=($ENV{'form.utarget'} eq 'export');
 
 # ------------------------------------------------------------------- Open page
 
@@ -1342,7 +1770,6 @@ sub handler {
 
 # --------------------------------------------------------------- Screen output
 
-  unless ($reroute) {
     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
     $r->print(<<ENDSCRIPT);
 <script language="JavaScript">
@@ -1367,84 +1794,78 @@ ENDSCRIPT
        &hiddenfield('usymb',$ENV{'form.usymb'}).
        &hiddenfield('unewfield','').
        &hiddenfield('unewformula',''));
-  }
+
+# ---------------------- Make sure that this gets out, even if user hits "stop"
+
     $r->rflush();
+
 # ---------------------------------------- Read new sheet or modified worksheet
 
-    my $sheetone=initsheet();
     $r->uri=~/\/(\w+)$/;
-    &settype($sheetone,$1);
+
+    my $asheet=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});
+
+# ------------------------ If a new formula had been entered, go from work copy
+
     if ($ENV{'form.unewfield'}) {
         $r->print('<h2>Modified Workcopy</h2>');
         $ENV{'form.unewformula'}=~s/\'/\"/g;
         $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.
                   $ENV{'form.unewformula'}.'<p>');
-        &setfilename($sheetone,$ENV{'form.ufn'});
-	&tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/',
-                 $ENV{'form.usymb'},
+        &setfilename($asheet,$ENV{'form.ufn'});
+	&tmpread($asheet,
                  $ENV{'form.unewfield'},$ENV{'form.unewformula'});
-    } elsif ($ENV{'form.saveas'}) {
-        &setfilename($sheetone,$ENV{'form.ufn'});
-	&tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/',
-                 $ENV{'form.usymb'});
+
+     } elsif ($ENV{'form.saveas'}) {
+        &setfilename($asheet,$ENV{'form.ufn'});
+	&tmpread($asheet);
     } else {
-        unless ($ENV{'form.ufn'}) {
+        &readsheet($asheet,$ENV{'form.ufn'});
     }
 
-  if (&gettype($sheetone) eq 'classcalc') {
-# ---------------------------------- For course view: get courselist and update
-       &updatestudentrows($sheetone);
-  } else {
-# ----------------- For assessment and student: See if all import rows uptodate
+# -------------------------------------------------- Print out user information
+
+    unless (&gettype($asheet) eq 'classcalc') {
+        $r->print('<p><b>User:</b> '.&getuname($asheet).
+                  '<br><b>Domain:</b> '.&getudom($asheet));
+        if (&getcsec($asheet) eq '-1') {
+           $r->print('<h3><font color=red>'.
+                     'Not a student in this course</font></h3>');
+        } else {
+           $r->print('<br><b>Section/Group:</b> '.&getcsec($asheet));
+        }
+    }
+
+# ---------------------------------------------------------------- Course title
+
+    $r->print('<h1>'.
+            $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.'</h1>');
+
 
-    if (tie(%parmhash,'GDBM_File',
-       $ENV{'request.course.fn'}.'_parms.db',&GDBM_READER,0640)) {
-       $csec=&Apache::lonnet::usection($udom,$uname,$ENV{'request.course.id'});
-       if ($csec eq '-1') {
-          $r->print('<h3><font color=red>'.
-   "User '$uname' at domain '$udom' not a student in this course</font></h3>");
-       }
-       &updaterows($sheetone);
-       untie(%parmhash);
-   } else {
-       $r->print('<h3><font color=red>'.
-	   'Could not initialize import fields (not in a course)</font></h3>');
-   }
- }
 # ---------------------------------------------------- See if something to save
+
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname='';
 	if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
             $fname=~s/\W/\_/g;
             if ($fname eq 'default') { $fname='course_default'; }
-            $fname.='_'.&gettype($sheetone);
-            &setfilename($sheetone,$fname);
+            $fname.='_'.&gettype($asheet);
+            &setfilename($asheet,$fname);
             $ENV{'form.ufn'}=$fname;
-            my $reply=&writesheet($sheetone);
-            unless ($reroute) {
-		$r->print('<p>Saving spreadsheet: '.$reply.'<p>');
-            }
-            if ($ENV{'form.makedefufn'}) {
-                my $reply=&Apache::lonnet::reply('put:'.
-                     $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
-                     $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.
-                     ':environment:spreadsheet_default_'.
-                     &gettype($sheetone).'='.
-                     &Apache::lonnet::escape($fname),
-                     $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
-               unless ($reroute) {
-	           $r->print('<p>Making default spreadsheet: '.$reply.'<p>');
-               }
-            }
-        }
+	    $r->print('<p>Saving spreadsheet: '.
+                         &writesheet($asheet,$ENV{'form.makedefufn'}).'<p>');
+	}
     }
+
 # ------------------------------------------------ Write the modified worksheet
 
-   &tmpwrite($sheetone,$r->dir_config('lonDaemons').'/tmp/',
-              $ENV{'form.usymb'});
+   $r->print('<b>Current sheet:</b> '.&getfilename($asheet).'<p>');
+
+   &tmpwrite($asheet);
+
+# ----------------------------------------------------------------- Save dialog
+
 
-# ----------------------------------------------------- Print user, course, etc
-   unless ($reroute) {
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname=$ENV{'form.ufn'};
         $fname=~s/\_[^\_]+$//;
@@ -1453,38 +1874,48 @@ ENDSCRIPT
               '<input type=text size=20 name=newfn value="'.$fname.
               '"> (make default: <input type=checkbox name="makedefufn">)<p>');
     }
-    $r->print(&hiddenfield('ufn',$ENV{'form.ufn'}));
-    unless (&gettype($sheetone) eq 'classcalc') {
-        $r->print('<br><b>User:</b> '.$uname.'<br><b>Domain:</b> '.$udom);
-    }
-    $r->print('<h1>'.
-            $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.'</h1>');
-    if ($csec) {
-       $r->print('<h3>Group/Section: '.$csec.'</h3>');
-    }
-   }
-# -------------------------------------------------------- Import and calculate
 
-    if (&gettype($sheetone) eq 'assesscalc') {
-	&rowaassess($sheetone,$ENV{'form.usymb'});
-    } elsif  (&gettype($sheetone) eq 'studentcalc') {
-	&rowazstudent($sheetone);
-    } else {
-        &rowazclass($sheetone);
-    }
-    my $calcoutput=&calcsheet($sheetone);
-    unless ($reroute) {
-       $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
+    $r->print(&hiddenfield('ufn',&getfilename($asheet)));
+
+# --------------------------------------------------------------- Cached sheets
+
+    &expirationdates();
+
+    undef %oldsheets;
+    undef %loadedcaches;
+
+    if (&gettype($asheet) eq 'classcalc') {
+        $r->print("Loading previously calculated student sheets ...<br>\n");
+        $r->rflush();
+        &cachedcsheets();
+    } elsif (&gettype($asheet) eq 'studentcalc') {
+        $r->print("Loading previously calculated assessment sheets ...<br>\n");
+        $r->rflush();
+        &cachedssheets(&getuname($asheet),&getudom($asheet),
+                       &getuhome($asheet));
     }
 
-# ------------------------------------------------------- Print or export sheet
-   unless ($reroute) {   
-    &outsheet($r,$sheetone);
+# ----------------------------------------------------- Update sheet, load rows
 
+    $r->print("Loaded sheet(s), updating rows ...<br>\n");
+    $r->rflush();
+
+    &updatesheet($asheet);
+
+    $r->print("Updated rows, loading row data ...<br>\n");
+    $r->rflush();
+
+    &loadrows($asheet,$r);
+
+    $r->print("Loaded row data, calculating sheet ...<br>\n");
+    $r->rflush();
+
+    my $calcoutput=&calcsheet($asheet);
+    $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
+
+    &outsheet($r,$asheet);
     $r->print('</form></body></html>');
-  } else {
-     $r->print(&exportrow($sheetone));
-  }
+
 # ------------------------------------------------------------------------ Done
   } else {
 # ----------------------------- Not in a course, or not allowed to modify parms
@@ -1493,23 +1924,8 @@ ENDSCRIPT
       return HTTP_NOT_ACCEPTABLE; 
   }
     return OK;
+
 }
 
 1;
 __END__
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-