--- loncom/lonnet/perl/lonnet.pm	2004/09/02 18:01:52	1.538
+++ loncom/lonnet/perl/lonnet.pm	2004/09/27 19:00:16	1.548
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.538 2004/09/02 18:01:52 albertel Exp $
+# $Id: lonnet.pm,v 1.548 2004/09/27 19:00:16 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -50,7 +50,7 @@ use Fcntl qw(:flock);
 use Apache::loncoursedata;
 use Apache::lonlocal;
 use Storable qw(lock_store lock_nstore lock_retrieve freeze thaw);
-use Time::HiRes();
+use Time::HiRes qw( gettimeofday tv_interval );
 my $readit;
 
 =pod
@@ -795,11 +795,11 @@ sub getsection {
         if ($key eq $courseid.'_st') { $section=''; }
         my ($dummy,$end,$start)=split(/\_/,&unescape($value));
         my $now=time;
-        if (defined($end) && ($now > $end)) {
+        if (defined($end) && $end && ($now > $end)) {
             $Expired{$end}=$section;
             next;
         }
-        if (defined($start) && ($now < $start)) {
+        if (defined($start) && $start && ($now < $start)) {
             $Pending{$start}=$section;
             next;
         }
@@ -821,15 +821,17 @@ sub getsection {
 }
 
 
-my $disk_caching_disabled=1;
+my $disk_caching_disabled=0;
 
 sub devalidate_cache {
     my ($cache,$id,$name) = @_;
     delete $$cache{$id.'.time'};
+    delete $$cache{$id.'.file'};
     delete $$cache{$id};
-    if ($disk_caching_disabled) { return; }
+    if (1 || $disk_caching_disabled) { return; }
     my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
-    open(DB,"$filename.lock");
+    if (!-e $filename) { return; }
+    open(DB,">$filename.lock");
     flock(DB,LOCK_EX);
     my %hash;
     if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
@@ -856,16 +858,32 @@ sub is_cached {
     my ($cache,$id,$name,$time) = @_;
     if (!$time) { $time=300; }
     if (!exists($$cache{$id.'.time'})) {
-	&load_cache_item($cache,$name,$id);
+	&load_cache_item($cache,$name,$id,$time);
     }
     if (!exists($$cache{$id.'.time'})) {
 #	&logthis("Didn't find $id");
 	return (undef,undef);
     } else {
 	if (time-($$cache{$id.'.time'})>$time) {
-#	    &logthis("Devalidating $id - ".time-($$cache{$id.'.time'}));
-	    &devalidate_cache($cache,$id,$name);
-	    return (undef,undef);
+	    if (exists($$cache{$id.'.file'})) {
+		foreach my $filename (@{ $$cache{$id.'.file'} }) {
+		    my $mtime=(stat($filename))[9];
+		    #+1 is to take care of edge effects
+		    if ($mtime && (($mtime+1) < ($$cache{$id.'.time'}))) {
+#			&logthis("Upping $mtime - ".$$cache{$id.'.time'}.
+#				 "$id because of $filename");
+		    } else {
+			&logthis("Devalidating $filename $id - ".(time-($$cache{$id.'.time'})));
+			&devalidate_cache($cache,$id,$name);
+			return (undef,undef);
+		    }
+		}
+		$$cache{$id.'.time'}=time;
+	    } else {
+#		&logthis("Devalidating $id - ".time-($$cache{$id.'.time'}));
+		&devalidate_cache($cache,$id,$name);
+		return (undef,undef);
+	    }
 	}
     }
     return ($$cache{$id},1);
@@ -881,44 +899,69 @@ sub do_cache {
     $$cache{$id};
 }
 
+my %do_save_item;
+my %do_save;
 sub save_cache_item {
     my ($cache,$name,$id)=@_;
     if ($disk_caching_disabled) { return; }
-    my $starttime=&Time::HiRes::time();
-#    &logthis("Saving :$name:$id");
-    my %hash;
-    my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
-    open(DB,"$filename.lock");
-    flock(DB,LOCK_EX);
-    if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
-	eval <<'EVALBLOCK';
-	    $hash{$id.'.time'}=$$cache{$id.'.time'};
-	    $hash{$id}=freeze({'item'=>$$cache{$id}});
+    $do_save{$name}=$cache;
+    if (!exists($do_save_item{$name})) { $do_save_item{$name}={} }
+    $do_save_item{$name}->{$id}=1;
+    return;
+}
+
+sub save_cache {
+    if ($disk_caching_disabled) { return; }
+    my ($cache,$name,$id);
+    foreach $name (keys(%do_save)) {
+	$cache=$do_save{$name};
+
+	my $starttime=&Time::HiRes::time();
+	&logthis("Saving :$name:");
+	my %hash;
+	my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
+	open(DB,">$filename.lock");
+	flock(DB,LOCK_EX);
+	if (tie(%hash,'GDBM_File',$filename,&GDBM_WRCREAT(),0640)) {
+	    foreach $id (keys(%{ $do_save_item{$name} })) {
+		eval <<'EVALBLOCK';
+		$hash{$id.'.time'}=$$cache{$id.'.time'};
+		$hash{$id}=freeze({'item'=>$$cache{$id}});
+		if (exists($$cache{$id.'.file'})) {
+		    $hash{$id.'.file'}=freeze({'item'=>$$cache{$id.'.file'}});
+		}
 EVALBLOCK
-        if ($@) {
-	    &logthis("<font color='red'>save_cache blew up :$@:$name</font>");
-	    unlink($filename);
-	}
-    } else {
-	if (-e $filename) {
-	    &logthis("Unable to tie hash (save cache item): $name ($!)");
-	    unlink($filename);
+                if ($@) {
+		    &logthis("<font color='red'>save_cache blew up :$@:$name</font>");
+		    unlink($filename);
+		    last;
+		}
+	    }
+	} else {
+	    if (-e $filename) {
+		&logthis("Unable to tie hash (save cache): $name ($!)");
+		unlink($filename);
+	    }
 	}
+	untie(%hash);
+	flock(DB,LOCK_UN);
+	close(DB);
+	&logthis("save_cache $name took ".(&Time::HiRes::time()-$starttime));
     }
-    untie(%hash);
-    flock(DB,LOCK_UN);
-    close(DB);
-#    &logthis("save_cache_item $name took ".(&Time::HiRes::time()-$starttime));
+    undef(%do_save);
+    undef(%do_save_item);
+
 }
 
 sub load_cache_item {
-    my ($cache,$name,$id)=@_;
+    my ($cache,$name,$id,$time)=@_;
     if ($disk_caching_disabled) { return; }
     my $starttime=&Time::HiRes::time();
 #    &logthis("Before Loading $name  for $id size is ".scalar(%$cache));
     my %hash;
     my $filename=$perlvar{'lonDaemons'}.'/tmp/lonnet_internal_cache_'.$name.".db";
-    open(DB,"$filename.lock");
+    if (!-e $filename) { return; }
+    open(DB,">$filename.lock");
     flock(DB,LOCK_SH);
     if (tie(%hash,'GDBM_File',$filename,&GDBM_READER(),0640)) {
 	eval <<'EVALBLOCK';
@@ -935,9 +978,17 @@ sub load_cache_item {
 		}
 #	    &logthis("Initial load: $count");
 	    } else {
-		my $hashref=thaw($hash{$id});
-		$$cache{$id}=$hashref->{'item'};
-		$$cache{$id.'.time'}=$hash{$id.'.time'};
+		if (($$cache{$id.'.time'}+$time) < time) {
+		    $$cache{$id.'.time'}=$hash{$id.'.time'};
+		    {
+			my $hashref=thaw($hash{$id});
+			$$cache{$id}=$hashref->{'item'};
+		    }
+		    if (exists($hash{$id.'.file'})) {
+			my $hashref=thaw($hash{$id.'.file'});
+			$$cache{$id.'.file'}=$hashref->{'item'};
+		    }
+		}
 	    }
 EVALBLOCK
         if ($@) {
@@ -1284,6 +1335,9 @@ sub clean_filename {
     $fname=~s/\s+/\_/g;
 # Replace all other weird characters by nothing
     $fname=~s/[^\w\.\-]//g;
+# Replace all .\d. sequences with _\d. so they no longer look like version
+# numbers
+    $fname=~s/\.(\d+)(?=\.)/_$1/g;
     return $fname;
 }
 
@@ -2720,7 +2774,9 @@ sub allowed {
     $uri=&deversion($uri);
     my $orguri=$uri;
     $uri=&declutter($uri);
-
+    
+    
+    
     if (defined($ENV{'allowed.'.$priv})) { return $ENV{'allowed.'.$priv}; }
 # Free bre access to adm and meta resources
     if (((($uri=~/^adm\//) && ($uri !~ m|/bulletinboard$|)) 
@@ -2728,6 +2784,13 @@ sub allowed {
 	return 'F';
     }
 
+# Free bre access to user's own portfolio contents
+    my ($space,$domain,$name,$dir)=split('/',$uri);
+    if (('uploaded' eq $space) && ($ENV{'user.name'} eq $name) && 
+	($ENV{'user.domain'} eq $domain) && ('portfolio' eq $dir)) {
+        return 'F';
+    }
+
 # Free bre to public access
 
     if ($priv eq 'bre') {
@@ -3129,8 +3192,10 @@ sub log_query {
 sub fetch_enrollment_query {
     my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_;
     my $homeserver;
+    my $maxtries = 1;
     if ($context eq 'automated') {
         $homeserver = $perlvar{'lonHostID'};
+        $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout
     } else {
         $homeserver = &homeserver($cnum,$dom);
     }
@@ -3148,8 +3213,13 @@ sub fetch_enrollment_query {
         return 'error: '.$queryid;
     }
     my $reply = &get_query_reply($queryid);
+    my $tries = 1;
+    while (($reply=~/^timeout/) && ($tries < $maxtries)) {
+        $reply = &get_query_reply($queryid);
+        $tries ++;
+    }
     if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) {
-        &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$ENV{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum);
+        &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$ENV{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries);
     } else {
         my @responses = split/:/,$reply;
         if ($homeserver eq $perlvar{'lonHostID'}) {
@@ -4026,11 +4096,14 @@ sub EXT {
 
 	my $section;
 	if (defined($courseid) && $courseid eq $ENV{'request.course.id'}) {
+	    if (!$symbparm) { $symbparm=&symbread(); }
+	}
+	if ($symbparm && defined($courseid) && 
+	    $courseid eq $ENV{'request.course.id'}) {
 
 	    #print '<br>'.$space.' - '.$qualifier.' - '.$spacequalifierrest;
 
 # ----------------------------------------------------- Cascading lookup scheme
-	    if (!$symbparm) { $symbparm=&symbread(); }
 	    my $symbp=$symbparm;
 	    my $mapp=(&decode_symb($symbp))[0];
 
@@ -4041,11 +4114,11 @@ sub EXT {
 		($ENV{'user.domain'} eq $udom)) {
 		$section=$ENV{'request.course.sec'};
 	    } else {
-                if (! defined($usection)) {
-                    $section=&usection($udom,$uname,$courseid);
-                } else {
-                    $section = $usection;
-                }
+		if (! defined($usection)) {
+		    $section=&usection($udom,$uname,$courseid);
+		} else {
+		    $section = $usection;
+		}
 	    }
 
 	    my $seclevel=$courseid.'.['.$section.'].'.$spacequalifierrest;
@@ -4083,7 +4156,7 @@ sub EXT {
 				 $uname." at ".$udom.": ".
 				 $tmp."</font>");
 		    } elsif ($tmp=~/error: 2 /) {
-                        &EXT_cache_set($udom,$uname);
+			&EXT_cache_set($udom,$uname);
 		    } elsif ($tmp =~ /^(con_lost|no_such_host)/) {
 			return $tmp;
 		    }
@@ -4093,10 +4166,10 @@ sub EXT {
 # -------------------------------------------------------- second, check course
 
 	    my $coursereply=&courseresdata($ENV{'course.'.$courseid.'.num'},
-					  $ENV{'course.'.$courseid.'.domain'},
-					  ($seclevelr,$seclevelm,$seclevel,
-					   $courselevelr,$courselevelm,
-					   $courselevel));
+					   $ENV{'course.'.$courseid.'.domain'},
+					   ($seclevelr,$seclevelm,$seclevel,
+					    $courselevelr,$courselevelm,
+					    $courselevel));
 	    if (defined($coursereply)) { return $coursereply; }
 
 # ------------------------------------------------------ third, check map parms
@@ -4231,7 +4304,9 @@ sub metadata {
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }
 	my $metastring;
 	if ($uri !~ m|^uploaded/|) {
-	    $metastring=&getfile(&filelocation('',&clutter($filename)));
+	    my $file=&filelocation('',&clutter($filename));
+	    push(@{$metacache{$uri.'.file'}},$file);
+	    $metastring=&getfile($file);
 	}
         my $parser=HTML::LCParser->new(\$metastring);
         my $token;
@@ -4596,14 +4671,20 @@ sub deversion {
 
 sub symbread {
     my ($thisfn,$donotrecurse)=@_;
+    my $cache_str='request.symbread.cached.'.$thisfn;
+    if (defined($ENV{$cache_str})) { return $ENV{$cache_str}; }
 # no filename provided? try from environment
     unless ($thisfn) {
-        if ($ENV{'request.symb'}) { return &symbclean($ENV{'request.symb'}); }
+        if ($ENV{'request.symb'}) {
+	    return $ENV{$cache_str}=&symbclean($ENV{'request.symb'});
+	}
 	$thisfn=$ENV{'request.filename'};
     }
 # is that filename actually a symb? Verify, clean, and return
     if ($thisfn=~/\_\_\_\d+\_\_\_(.*)$/) {
-	if (&symbverify($thisfn,$1)) { return &symbclean($thisfn); }
+	if (&symbverify($thisfn,$1)) {
+	    return $ENV{$cache_str}=&symbclean($thisfn);
+	}
     }
     $thisfn=declutter($thisfn);
     my %hash;
@@ -4624,7 +4705,7 @@ sub symbread {
            unless ($syval=~/\_\d+$/) {
 	       unless ($ENV{'form.request.prefix'}=~/\.(\d+)\_$/) {
                   &appenv('request.ambiguous' => $thisfn);
-                  return '';
+		  return $ENV{$cache_str}='';
                }    
                $syval.=$1;
 	   }
@@ -4671,11 +4752,11 @@ sub symbread {
            }
         }
         if ($syval) {
-           return &symbclean($syval.'___'.$thisfn); 
+	    return $ENV{$cache_str}=&symbclean($syval.'___'.$thisfn);
         }
     }
     &appenv('request.ambiguous' => $thisfn);
-    return '';
+    return $ENV{$cache_str}='';
 }
 
 # ---------------------------------------------------------- Return random seed