--- loncom/interface/loncoursedata.pm 2002/08/29 03:04:10 1.25 +++ loncom/interface/loncoursedata.pm 2003/01/10 21:50:17 1.41 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # (Publication Handler # -# $Id: loncoursedata.pm,v 1.25 2002/08/29 03:04:10 stredwic Exp $ +# $Id: loncoursedata.pm,v 1.41 2003/01/10 21:50:17 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -139,7 +139,7 @@ sub DownloadClasslist { %classlist=&Apache::lonnet::dump('classlist',$courseDomain, $courseNumber); foreach(keys (%classlist)) { if(/^(con_lost|error|no_such_host)/i) { - return \%classlist; + return; } } @@ -309,17 +309,29 @@ sub ProcessTopResourceMap { return 'Can not open Coursemap.'; } + my $oldkeys; + delete $cache->{'OptionResponses'}; + if(defined($cache->{'ResourceKeys'})) { + $oldkeys = $cache->{'ResourceKeys'}; + foreach (split(':::', $cache->{'ResourceKeys'})) { + delete $cache->{$_}; + } + delete $cache->{'ResourceKeys'}; + } + # Initialize state machine. Set information pointing to top level map. my (@sequences, @currentResource, @finishResource); my ($currentSequence, $currentResourceID, $lastResourceID); - $currentResourceID=$hash{'ids_/res/'.$ENV{'request.course.uri'}}; + $currentResourceID=$hash{'ids_'. + &Apache::lonnet::clutter($ENV{'request.course.uri'})}; push(@currentResource, $currentResourceID); $lastResourceID=-1; $currentSequence=-1; my $topLevelSequenceNumber = $currentSequence; my %sequenceRecord; + my %allkeys; while(1) { if($c->aborted()) { last; @@ -344,6 +356,7 @@ sub ProcessTopResourceMap { } else { $cache->{'orderedSequences'}.=':'.$currentSequence; } + $allkeys{'orderedSequences'}++; $lastResourceID=$hash{'map_finish_'. $hash{'src_'.$currentResourceID}}; @@ -378,20 +391,24 @@ sub ProcessTopResourceMap { $currentResourceID})); $cache->{$currentResourceID.':problem'}=$Problem; + $allkeys{$currentResourceID.':problem'}++; if(!defined($cache->{$currentSequence.':problems'})) { $cache->{$currentSequence.':problems'}=$currentResourceID; } else { $cache->{$currentSequence.':problems'}.= ':'.$currentResourceID; } + $allkeys{$currentSequence.':problems'}++; my $meta=$hash{'src_'.$currentResourceID}; # $cache->{$currentResourceID.':title'}= # &Apache::lonnet::metdata($meta,'title'); $cache->{$currentResourceID.':title'}= $hash{'title_'.$currentResourceID}; + $allkeys{$currentResourceID.':title'}++; $cache->{$currentResourceID.':source'}= $hash{'src_'.$currentResourceID}; + $allkeys{$currentResourceID.':source'}++; # Get Parts for problem my %beenHere; @@ -408,6 +425,8 @@ sub ProcessTopResourceMap { $cache->{$currentSequence.':'.$currentResourceID. ':parts'}.=':'.$partId; } + $allkeys{$currentSequence.':'.$currentResourceID. + ':parts'}++; } if($beenHere{'r:'.$partId.':'.$responseId} == 0) { $beenHere{'r:'.$partId.':'.$responseId}++; @@ -421,6 +440,8 @@ sub ProcessTopResourceMap { ':'.$partId.':responseIDs'}.=':'. $responseId; } + $allkeys{$currentSequence.':'.$currentResourceID.':'. + $partId.':responseIDs'}++; } if(/^optionresponse/ && $beenHere{'o:'.$partId.':'.$currentResourceID} == 0) { @@ -434,6 +455,7 @@ sub ProcessTopResourceMap { $currentResourceID.':'. $partId.':'.$responseId; } + $allkeys{'OptionResponses'}++; } } } @@ -449,8 +471,10 @@ sub ProcessTopResourceMap { # Capture sequence information here $cache->{$currentSequence.':title'}= $hash{'title_'.$currentResourceID}; + $allkeys{$currentSequence.':title'}++; $cache->{$currentSequence.':source'}= $hash{'src_'.$currentResourceID}; + $allkeys{$currentSequence.':source'}++; my $totalProblems=0; foreach my $currentProblem (split(/\:/, @@ -464,9 +488,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; @@ -474,6 +498,7 @@ sub ProcessTopResourceMap { $cache->{$currentSequence.':columnWidth'}= (scalar @titleLength); } + $allkeys{$currentSequence.':columnWidth'}++; } else { # Remove sequence from list, if it contains no problems to # display. @@ -494,7 +519,7 @@ 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; } @@ -510,6 +535,15 @@ sub ProcessTopResourceMap { $currentResourceID=pop(@currentResource); } + my @theKeys = keys(%allkeys); + my $newkeys = join(':::', @theKeys); + $cache->{'ResourceKeys'} = join(':::', $newkeys); + if($newkeys ne $oldkeys) { + $cache->{'ResourceUpdated'} = 'true'; + } else { + $cache->{'ResourceUpdated'} = 'false'; + } + unless (untie(%hash)) { &Apache::lonnet::logthis("WARNING: ". "Could not untie coursemap $fn (browse)". @@ -553,7 +587,24 @@ browser Output: @names @names: An array of students whose information has been processed, and are to -be considered in an arbitrary order. +be considered in an arbitrary order. The entries in @names are of the form +username:domain. + +The values in $cache are as follows: + + *NOTE: for the following $name implies username:domain + $name.':error' only defined if an error occured. Value + contains the error message + $name.':lastDownloadTime' unconverted time of the last update of a + student\'s course data + $name.'updateTime' coverted time of the last update of a + student\'s course data + $name.':username' username of a student + $name.':domain' domain of a student + $name.':fullname' full name of a student + $name.':id' PID of a student + $name.':Status' active/expired status of a student + $name.':section' section of a student =back @@ -576,9 +627,8 @@ sub ProcessClasslist { if($c->aborted()) { return (); } - my $studentInformation = $classlist->{$name.':studentInformation'}, - my $sectionData = $classlist->{$name.':sections'}, - my $date = $classlist->{$name}, + my $studentInformation = $classlist->{$name.':studentInformation'}; + my $date = $classlist->{$name}; my ($studentName,$studentDomain) = split(/\:/,$name); $cache->{$name.':username'}=$studentName; @@ -613,6 +663,7 @@ sub ProcessClasslist { $courseID=~s/^(\w)/\/$1/; my $sec=''; + my $sectionData = $classlist->{$name.':sections'}; foreach my $key (keys (%$sectionData)) { my $value = $sectionData->{$key}; if ($key=~/^$courseID(?:\/)*(\w+)*\_st$/) { @@ -620,7 +671,7 @@ sub ProcessClasslist { if($key eq $courseID.'_st') { $tempsection=''; } - my ($dummy,$roleend,$rolestart)=split(/\_/,$value); + my (undef,$roleend,$rolestart)=split(/\_/,$value); if($roleend eq $end && $rolestart eq $start) { $sec = $tempsection; last; @@ -693,11 +744,26 @@ sub ProcessStudentData { return; } + # This little delete thing, should not be here. Move some other + # time though. + if(defined($cache->{$name.':keys'})) { + foreach (split(':::', $cache->{$name.':keys'})) { + delete $cache->{$name.':'.$_}; + } + delete $cache->{$name.':keys'}; + } + + my %courseKeys; # user name:domain was prepended earlier in DownloadCourseInformation foreach (keys %$courseData) { + my $currentKey = $_; + $currentKey =~ s/^$name//; + $courseKeys{$currentKey}++; $cache->{$_}=$courseData->{$_}; } + $cache->{$name.':keys'} = join(':::', keys(%courseKeys)); + return; } @@ -753,6 +819,16 @@ sub ExtractStudentData { return; } + # This little delete thing, should not be here. Move some other + # time though. + my %allkeys; + if(defined($output->{$name.':keys'})) { + foreach (split(':::', $output->{$name.':keys'})) { + delete $output->{$name.':'.$_}; + } + delete $output->{$name.':keys'}; + } + my ($username,$domain)=split(':',$name); my $Version; @@ -768,6 +844,8 @@ sub ExtractStudentData { # Output dashes for all the parts of this problem if there # is no version information about the current problem. + $output->{$name.':'.$problemID.':NoVersion'} = 'false'; + $allkeys{$name.':'.$problemID.':NoVersion'}++; if(!$LatestVersion) { foreach my $part (split(/\:/,$data->{$sequence.':'. $problemID. @@ -775,6 +853,9 @@ sub ExtractStudentData { $output->{$name.':'.$problemID.':'.$part.':tries'} = 0; $output->{$name.':'.$problemID.':'.$part.':awarded'} = 0; $output->{$name.':'.$problemID.':'.$part.':code'} = ' '; + $allkeys{$name.':'.$problemID.':'.$part.':tries'}++; + $allkeys{$name.':'.$problemID.':'.$part.':awarded'}++; + $allkeys{$name.':'.$problemID.':'.$part.':code'}++; $totalProblems++; } $output->{$name.':'.$problemID.':NoVersion'} = 'true'; @@ -860,6 +941,7 @@ sub ExtractStudentData { ':parts'})) { $output->{$name.':'.$problemID.':'.$part.':wrong'} = $partData{$part.':tries'}; + $allkeys{$name.':'.$problemID.':'.$part.':wrong'}++; if($partData{$part.':code'} eq '*') { $output->{$name.':'.$problemID.':'.$part.':wrong'}--; @@ -875,14 +957,22 @@ sub ExtractStudentData { $partData{$part.':code'}; $output->{$name.':'.$problemID.':'.$part.':awarded'} = $partData{$part.':awarded'}; + $allkeys{$name.':'.$problemID.':'.$part.':tries'}++; + $allkeys{$name.':'.$problemID.':'.$part.':code'}++; + $allkeys{$name.':'.$problemID.':'.$part.':awarded'}++; + $totalAwarded += $partData{$part.':awarded'}; $output->{$name.':'.$problemID.':'.$part.':timestamp'} = $partData{$part.':timestamp'}; + $allkeys{$name.':'.$problemID.':'.$part.':timestamp'}++; + foreach my $response (split(':', $data->{$sequence.':'. $problemID.':'. $part.':responseIDs'})) { $output->{$name.':'.$problemID.':'.$part.':'.$response. ':submission'}=join(':::',@submissions); + $allkeys{$name.':'.$problemID.':'.$part.':'.$response. + ':submission'}++; } if($partData{$part.':code'} ne 'x') { @@ -892,6 +982,7 @@ sub ExtractStudentData { } $output->{$name.':'.$sequence.':problemsCorrect'} = $problemsCorrect; + $allkeys{$name.':'.$sequence.':problemsCorrect'}++; $problemsSolved += $problemsCorrect; $problemsCorrect=0; } @@ -899,6 +990,11 @@ sub ExtractStudentData { $output->{$name.':problemsSolved'} = $problemsSolved; $output->{$name.':totalProblems'} = $totalProblems; $output->{$name.':totalAwarded'} = $totalAwarded; + $allkeys{$name.':problemsSolved'}++; + $allkeys{$name.':totalProblems'}++; + $allkeys{$name.':totalAwarded'}++; + + $output->{$name.':keys'} = join(':::', keys(%allkeys)); return; } @@ -989,37 +1085,22 @@ sub ProcessFullName { my ($lastname, $generation, $firstname, $middlename)=@_; my $Str = ''; + # Strip whitespace preceeding & following name components. + $lastname =~ s/(\s+$|^\s+)//g; + $generation =~ s/(\s+$|^\s+)//g; + $firstname =~ s/(\s+$|^\s+)//g; + $middlename =~ s/(\s+$|^\s+)//g; + if($lastname ne '') { - $Str .= $lastname.' '; - if($generation ne '') { - $Str .= $generation; - } else { - chop($Str); - } - $Str .= ', '; - if($firstname ne '') { - $Str .= $firstname.' '; - } - if($middlename ne '') { - $Str .= $middlename; - } else { - chop($Str); - if($firstname eq '') { - chop($Str); - } - } + $Str .= $lastname; + $Str .= ' '.$generation if ($generation ne ''); + $Str .= ','; + $Str .= ' '.$firstname if ($firstname ne ''); + $Str .= ' '.$middlename if ($middlename ne ''); } else { - if($firstname ne '') { - $Str .= $firstname.' '; - } - if($middlename ne '') { - $Str .= $middlename.' '; - } - if($generation ne '') { - $Str .= $generation; - } else { - chop($Str); - } + $Str .= $firstname if ($firstname ne ''); + $Str .= ' '.$middlename if ($middlename ne ''); + $Str .= ' '.$generation if ($generation ne ''); } return $Str; @@ -1122,17 +1203,19 @@ sub DownloadStudentCourseData { } my $downloadTime='Not downloaded'; + my $needUpdate = 'false'; if($checkDate eq 'true' && tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { $downloadTime = $cache{$_.':lastDownloadTime'}; + $needUpdate = $cache{'ResourceUpdated'}; untie(%cache); } if($c->aborted()) { return 'Aborted'; } - #if($downloadTime ne 'Not downloaded') { - # next; - #} + if($needUpdate eq 'true') { + $downloadTime = 'Not downloaded'; + } my $courseData = &DownloadCourseInformation($_, $courseID, $downloadTime, $WhatIWant); @@ -1171,7 +1254,7 @@ sub DownloadStudentCourseDataSeparate { $WhatIWant .= '|timestamp)'; $WhatIWant .= ')'; - &CheckForResidualDownload($courseID, $cacheDB, $students, $c); + &CheckForResidualDownload($cacheDB, 'true', 'true', $courseID, $r, $c); my $studentCount = scalar(@$students); if($status eq 'true') { @@ -1192,9 +1275,11 @@ sub DownloadStudentCourseDataSeparate { my %cache; my $downloadTime='Not downloaded'; + my $needUpdate = 'false'; if($checkDate eq 'true' && tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { $downloadTime = $cache{$_.':lastDownloadTime'}; + $needUpdate = $cache{'ResourceUpdated'}; untie(%cache); } @@ -1202,31 +1287,33 @@ sub DownloadStudentCourseDataSeparate { return 'Aborted'; } - #if($downloadTime eq 'Not downloaded') { - my $error = 0; - my $courseData = - &DownloadCourseInformation($_, $courseID, $downloadTime, - $WhatIWant); - my %downloadData; - unless(tie(%downloadData,'GDBM_File',$residualFile, - &GDBM_WRCREAT(),0640)) { - return 'Failed to tie temporary download hash.'; - } - foreach my $key (keys(%$courseData)) { - $downloadData{$key} = $courseData->{$key}; - if($key =~ /^(con_lost|error|no_such_host)/i) { - $error = 1; - last; - } + if($needUpdate eq 'true') { + $downloadTime = 'Not downloaded'; + } + + my $error = 0; + my $courseData = + &DownloadCourseInformation($_, $courseID, $downloadTime, + $WhatIWant); + my %downloadData; + unless(tie(%downloadData,'GDBM_File',$residualFile, + &GDBM_WRCREAT(),0640)) { + return 'Failed to tie temporary download hash.'; + } + foreach my $key (keys(%$courseData)) { + $downloadData{$key} = $courseData->{$key}; + if($key =~ /^(con_lost|error|no_such_host)/i) { + $error = 1; + last; } - if($error) { - foreach my $deleteKey (keys(%$courseData)) { - delete $downloadData{$deleteKey}; - } - $downloadData{$_.':error'} = 'No course data for '.$_; + } + if($error) { + foreach my $deleteKey (keys(%$courseData)) { + delete $downloadData{$deleteKey}; } - untie(%downloadData); - #} + $downloadData{$_.':error'} = 'No course data for '.$_; + } + untie(%downloadData); } if($status eq 'true') { &Apache::lonhtmlcommon::Close_PrgWin($r); } @@ -1301,7 +1388,95 @@ sub CheckForResidualDownload { return 'OK'; } +################################################ +################################################ + +=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__ + +