--- loncom/interface/loncoursedata.pm 2002/10/09 17:25:23 1.34 +++ loncom/interface/loncoursedata.pm 2003/02/20 14:28:25 1.49 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # (Publication Handler # -# $Id: loncoursedata.pm,v 1.34 2002/10/09 17:25:23 matthew Exp $ +# $Id: loncoursedata.pm,v 1.49 2003/02/20 14:28:25 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -218,7 +218,8 @@ sub DownloadCourseInformation { $courseID.'.db', $Apache::lonnet::perlvar{'lonUsersDir'}); - if($lastDownloadTime >= $modifiedTime && $modifiedTime >= 0) { + if($lastDownloadTime ne 'Not downloaded' && + $lastDownloadTime >= $modifiedTime && $modifiedTime >= 0) { # Data is not gathered so return UpToDate as true. This # will be interpreted in ProcessClasslist $courseData{$namedata.':lastDownloadTime'}=time; @@ -260,6 +261,117 @@ with stopping downloading then can not t # ----- PROCESSING FUNCTIONS --------------------------------------- + + +=pod + +=item &get_sequence_assessment_data() + +AT THIS TIME THE USE OF THIS FUNCTION IS *NOT* RECOMMENDED + +Use lonnavmaps to build a data structure describing the order and +assessment contents of each sequence in the current course. + +The returned structure is a hash reference. + +{ title => 'title', + symb => 'symb', + source => '/s/o/u/r/c/e', + type => (container|assessment), + contents => [ {},{},{},{} ], # only for container + parts => [11,13,15], # only for assessment + response_ids => [12,14,16] # only for assessment +} + +Also returned are array references to the symbs and assessments contained +in the sequence. + +$hash->{'contents'} is a reference to an array of hashes of the same structure. + +=cut + +sub get_sequence_assessment_data { + return undef; + my $fn=$ENV{'request.course.fn'}; + &Apache::lonnet::logthis('filename = '.$fn); + ## + ## use navmaps + my $navmap = Apache::lonnavmaps::navmap->new($fn.".db",$fn."_parms.db", + 1,0); + if (!defined($navmap)) { + return 'Can not open Coursemap'; + } + my $iterator = $navmap->getIterator(undef, undef, undef, 1); + ## + ## Prime the pump + ## + ## We are going to loop until we run out of sequences/pages to explore for + ## resources. This means we have to start out with something to look + ## at. + my $curRes = $iterator->next(); # BEGIN_MAP + $curRes = $iterator->next(); # The sequence itself + # + my $title = $curRes->title(); + my $symb = $curRes->symb(); + my $src = $curRes->src(); + # + my @Sequences; + my @Assessments; + my @Nested_Sequences = (); # Stack of sequences, keeps track of depth + my $top = { title => $title, + symb => $symb, + type => 'container', + num_assess => 0, + contents => [], }; + push (@Sequences,$top); + push (@Nested_Sequences, $top); + # + # We need to keep track of which sequences contain homework problems + # + while (scalar(@Nested_Sequences)) { + $curRes = $iterator->next(); + my $currentmap = $Nested_Sequences[-1]; # Last one on the stack + if ($curRes == $iterator->BEGIN_MAP()) { + # get the map itself, instead of BEGIN_MAP + $curRes = $iterator->next(); + $title = $curRes->title(); + $symb = $curRes->symb(); + $src = $curRes->src(); + my $newmap = { title => $title, + src => $src, + symb => $symb, + type => 'container', + num_assess => 0, + contents => [], + }; + push (@{$currentmap->{'contents'}},$newmap); # this is permanent + push (@Sequences,$newmap); + push (@Nested_Sequences, $newmap); # this is a stack + next; + } + if ($curRes == $iterator->END_MAP()) { + pop(@Nested_Sequences); + next; + } + next if (! ref($curRes)); + next if (! $curRes->is_problem() && !$curRes->randomout); + # Okay, from here on out we only deal with assessments + $title = $curRes->title(); + $symb = $curRes->symb(); + $src = $curRes->src(); + my $parts = $curRes->parts(); + my $assessment = { title => $title, + src => $src, + symb => $symb, + type => 'assessment', + }; + push(@Assessments,$assessment); + push(@{$currentmap->{'contents'}},$assessment); + $currentmap->{'num_assess'}++; + } + return ($top,\@Sequences,\@Assessments); +} + =pod =item &ProcessTopResourceMap() @@ -310,6 +422,7 @@ sub ProcessTopResourceMap { } my $oldkeys; + delete $cache->{'OptionResponses'}; if(defined($cache->{'ResourceKeys'})) { $oldkeys = $cache->{'ResourceKeys'}; foreach (split(':::', $cache->{'ResourceKeys'})) { @@ -487,9 +600,9 @@ sub ProcessTopResourceMap { } my @titleLength=split(//,$cache->{$currentSequence. ':title'}); - # $extra is 3 for problems correct and 3 for space + # $extra is 5 for problems correct and 3 for space # between problems correct and problem output - my $extra = 6; + my $extra = 8; if(($totalProblems + $extra) > (scalar @titleLength)) { $cache->{$currentSequence.':columnWidth'}= $totalProblems + $extra; @@ -518,9 +631,9 @@ sub ProcessTopResourceMap { # big problem, need to handle. Next is probably wrong my $errorMessage = 'Big problem in '; $errorMessage .= 'loncoursedata::ProcessTopLevelMap.'; - $errorMessage .= ' bighash to_$currentResourceID not defined!'; + $errorMessage .= " bighash to_$currentResourceID not defined!"; &Apache::lonnet::logthis($errorMessage); - last; + if (!defined($currentResourceID)) {last;} } my @nextResources=(); foreach (split(/\,/,$hash{'to_'.$currentResourceID})) { @@ -1179,7 +1292,7 @@ sub DownloadStudentCourseData { my $WhatIWant; $WhatIWant = '(^version:|'; $WhatIWant .= '^\d+:.+?:(resource\.\d+\.'; - $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$'; + $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$';#' $WhatIWant .= '|timestamp)'; $WhatIWant .= ')'; # $WhatIWant = '.'; @@ -1242,14 +1355,14 @@ sub DownloadStudentCourseData { sub DownloadStudentCourseDataSeparate { my ($students,$checkDate,$cacheDB,$extract,$status,$courseID,$r,$c)=@_; - my $residualFile = '/home/httpd/perl/tmp/'.$courseID.'DownloadFile.db'; + my $residualFile = $Apache::lonnet::tmpdir.$courseID.'DownloadFile.db'; my $title = 'LON-CAPA Statistics'; my $heading = 'Download Course Data'; my $WhatIWant; $WhatIWant = '(^version:|'; $WhatIWant .= '^\d+:.+?:(resource\.\d+\.'; - $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$'; + $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$';#' $WhatIWant .= '|timestamp)'; $WhatIWant .= ')'; @@ -1323,7 +1436,7 @@ sub DownloadStudentCourseDataSeparate { sub CheckForResidualDownload { my ($cacheDB,$extract,$status,$courseID,$r,$c)=@_; - my $residualFile = '/home/httpd/perl/tmp/'.$courseID.'DownloadFile.db'; + my $residualFile = $Apache::lonnet::tmpdir.$courseID.'DownloadFile.db'; if(!-e $residualFile) { return 'OK'; } @@ -1387,7 +1500,253 @@ sub CheckForResidualDownload { return 'OK'; } + +################################################ +################################################ + +=pod + +=item &make_into_hash($values); + +Returns a reference to a hash as described by $values. $values is +assumed to be the result of + join(':',map {&Apache::lonnet::escape($_)} %orighash; + +This is a helper function for get_current_state. + +=cut + +################################################ +################################################ +sub make_into_hash { + my $values = shift; + my %tmp = map { &Apache::lonnet::unescape($_); } + split(':',$values); + return \%tmp; +} + + +################################################ +################################################ + +=pod + +=item &get_current_state($sname,$sdom,$symb,$courseid); + +Retrieve the current status of a students performance. $sname and +$sdom are the only required parameters. If $symb is undef the results +of an &Apache::lonnet::currentdump() will be returned. +If $courseid is undef it will be retrieved from the environment. + +The return structure is based on &Apache::lonnet::currentdump. If +$symb is unspecified, all the students data is returned in a hash of +the form: +( + symb1 => { param1 => value1, param2 => value2 ... }, + symb2 => { param1 => value1, param2 => value2 ... }, +) + +If $symb is specified, a hash of +( + param1 => value1, + param2 => value2, +) +is returned. + +If no data is found for $symb, or if the student has not performance data, +an empty list is returned. + +=cut + +################################################ +################################################ +sub get_current_state { + my ($sname,$sdom,$symb,$courseid,$forcedownload)=@_; + return () if (! defined($sname) || ! defined($sdom)); + # + $courseid = $ENV{'request.course.id'} if (! defined($courseid)); + # + my $cachefilename = $Apache::lonnet::tmpdir.$ENV{'user.name'}.'_'. + $ENV{'user.domain'}.'_'. + $courseid.'_student_data.db'; + my %cache; + # + my %student_data; # return values go here + # + my $updatetime = 0; + my $key = &Apache::lonnet::escape($sname).':'. + &Apache::lonnet::escape($sdom).':'; + # Open the cache file + if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) { + if (exists($cache{$key.'time'})) { + $updatetime = $cache{$key.'time'}; +# &Apache::lonnet::logthis('got updatetime of '.$updatetime); + } + untie(%cache); + } + # timestamp/devalidation + my $modifiedtime = 1; + # Take whatever steps are neccessary at this point to give $modifiedtime a + # new value + # + if (($updatetime < $modifiedtime) || + (defined($forcedownload) && $forcedownload)) { +# &Apache::lonnet::logthis("loading data"); + # Get all the students current data + my $time_of_retrieval = time; + my @tmp = &Apache::lonnet::currentdump($courseid,$sdom,$sname); + if ((scalar(@tmp) > 0) && ($tmp[0] =~ /^error:/)) { + &Apache::lonnet::logthis('error getting data for '. + $sname.':'.$sdom.' in course '.$courseid. + ':'.$tmp[0]); + return (); + } + %student_data = @tmp; + # + # Store away the data + # + # The cache structure is colon deliminated. + # $uname:$udom:time => timestamp + # $uname:$udom:$symb => $parm1:$val1:$parm2:$val2 ... + # + # BEWARE: The colons are NOT escaped so can search with escaped + # keys instead of unescaping every key. + # + if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_WRCREAT(),0640)) { +# &Apache::lonnet::logthis("writing data"); + while (my ($current_symb,$param_hash) = each(%student_data)) { + my @Parameters = %{$param_hash}; + my $value = join(':',map { &Apache::lonnet::escape($_); } + @Parameters); + # Store away the values + $cache{$key.&Apache::lonnet::escape($current_symb)}=$value; + } + $cache{$key.'time'}=$time_of_retrieval; + untie(%cache); + } + } else { + &Apache::lonnet::logthis('retrieving cached data '); + if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) { + if (defined($symb)) { + my $searchkey = $key.&Apache::lonnet::escape($symb); + if (exists($cache{$searchkey})) { + $student_data{$symb} = &make_into_hash($cache{$searchkey}); + } + } else { + my $searchkey = '^'.$key.'(.*)$';#' + while (my ($testkey,$params)=each(%cache)) { + if ($testkey =~ /$searchkey/) { # \Q \E? May be necc. + my $tmpsymb = $1; + next if ($tmpsymb =~ 'time'); +# &Apache::lonnet::logthis('found '.$tmpsymb.':'); + $student_data{&Apache::lonnet::unescape($tmpsymb)} = + &make_into_hash($params); + } + } + } + untie(%cache); + } + } + if (! defined($symb)) { +# &Apache::lonnet::logthis("returning all data"); + return %student_data; + } elsif (exists($student_data{$symb})) { +# &Apache::lonnet::logthis("returning data for symb=".$symb); + return %{$student_data{$symb}}; + } else { + return (); + } +} + +################################################ +################################################ + +=pod + +=item &get_classlist(); + +Retrieve the classist of a given class or of the current class. Student +information is returned from the classlist.db file and, if needed, +from the students environment. + +Optional arguments are $cid, $cdom, and $cnum (course id, course domain, +and course number, respectively). Any omitted arguments will be taken +from the current environment ($ENV{'request.course.id'}, +$ENV{'course.'.$cid.'.domain'}, and $ENV{'course.'.$cid.'.num'}). + +Returns a reference to a hash which contains: + keys '$sname:$sdom' + values [$end,$start,$id,$section,$fullname] + +=cut + +################################################ +################################################ + +sub get_classlist { + my ($cid,$cdom,$cnum) = @_; + $cid = $cid || $ENV{'request.course.id'}; + $cdom = $cdom || $ENV{'course.'.$cid.'.domain'}; + $cnum = $cnum || $ENV{'course.'.$cid.'.num'}; + my $now = time; + # + my %classlist=&Apache::lonnet::dump('classlist',$cdom,$cnum); + while (my ($student,$info) = each(%classlist)) { + return undef if ($student =~ /^(con_lost|error|no_such_host)/i); + my ($sname,$sdom) = split(/:/,$student); + my @Values = split(/:/,$info); + my ($end,$start,$id,$section,$fullname); + if (@Values > 2) { + ($end,$start,$id,$section,$fullname) = @Values; + } else { # We have to get the data ourselves + ($end,$start) = @Values; + $section = &Apache::lonnet::getsection($sdom,$sname,$cid); + my %info=&Apache::lonnet::get('environment', + ['firstname','middlename', + 'lastname','generation','id'], + $sdom, $sname); + my ($tmp) = keys(%info); + if ($tmp =~/^(con_lost|error|no_such_host)/i) { + $fullname = 'not available'; + $id = 'not available'; + &Apache::lonnet::logthis('unable to retrieve environment '. + 'for '.$sname.':'.$sdom); + } else { + $fullname = &ProcessFullName(@info{qw/lastname generation + firstname middlename/}); + $id = $info{'id'}; + } + # Update the classlist with this students information + if ($fullname ne 'not available') { + my $enrolldata = join(':',$end,$start,$id,$section,$fullname); + my $reply=&Apache::lonnet::cput('classlist', + {$student => $enrolldata}, + $cdom,$cnum); + if ($reply !~ /^(ok|delayed)/) { + &Apache::lonnet::logthis('Unable to update classlist for '. + 'student '.$sname.':'.$sdom. + ' error:'.$reply); + } + } + } + my $status='Expired'; + if(((!$end) || $now < $end) && ((!$start) || ($now > $start))) { + $status='Active'; + } + $classlist{$student} = + [$sdom,$sname,$end,$start,$id,$section,$fullname,$status]; + } + if (wantarray()) { + return (\%classlist,['domain','username','end','start','id', + 'section','fullname','status']); + } else { + return \%classlist; + } +} + # ----- END HELPER FUNCTIONS -------------------------------------------- 1; __END__ + +