--- loncom/interface/loncoursedata.pm 2003/03/05 14:39:08 1.56 +++ loncom/interface/loncoursedata.pm 2003/03/17 22:52:04 1.57 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: loncoursedata.pm,v 1.56 2003/03/05 14:39:08 matthew Exp $ +# $Id: loncoursedata.pm,v 1.57 2003/03/17 22:52:04 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -52,6 +52,8 @@ use strict; use Apache::Constants qw(:common :http); use Apache::lonnet(); use Apache::lonhtmlcommon; +use Time::HiRes; +use Apache::lonmysql; use HTML::TokeParser; use GDBM_File; @@ -1524,7 +1526,7 @@ sub CheckForResidualDownload { Returns a reference to a hash as described by $values. $values is assumed to be the result of - join(':',map {&Apache::lonnet::escape($_)} %orighash; + join(':',map {&Apache::lonnet::escape($_)} %orighash); This is a helper function for get_current_state. @@ -1545,7 +1547,829 @@ sub make_into_hash { =pod -=item &get_current_state($sname,$sdom,$symb,$courseid); +=head1 LOCAL DATA CACHING SUBROUTINES + +The local caching is done using MySQL. There is no fall-back implementation +if MySQL is not running. + +The programmers interface is to call &get_current_state() or some other +primary interface subroutine (described below). The internals of this +storage system are documented here. + +There are six tables used to store student performance data (the results of +a dumpcurrent). Each of these tables is created in MySQL with a name of +$courseid_*****, where ***** is 'symb', 'part', or whatever is appropriate +for the table. The tables and their purposes are described below. + +Some notes before we get started. + +Each table must have a PRIMARY KEY, which is a column or set of columns which +will serve to uniquely identify a row of data. NULL is not allowed! + +INDEXes work best on integer data. + +JOIN is used to combine data from many tables into one output. + +lonmysql.pm is used for some of the interface, specifically the table creation +calls. The inserts are done in bulk by directly calling the database handler. +The SELECT ... JOIN statement used to retrieve the data does not have an +interface in lonmysql.pm and I shudder at the thought of writing one. + +=head3 Table Descriptions + +=over 4 + +=item $symb_table + +The symb_table has two columns. The first is a 'symb_id' and the second +is the text name for the 'symb' (limited to 64k). The 'symb_id' is generated +automatically by MySQL so inserts should be done on this table with an +empty first element. This table has its PRIMARY KEY on the 'symb_id'. + +=item $part_table + +The part_table has two columns. The first is a 'part_id' and the second +is the text name for the 'part' (limited to 100 characters). The 'part_id' is +generated automatically by MySQL so inserts should be done on this table with +an empty first element. This table has its PRIMARY KEY on the 'part' (100 +characters) and a KEY on 'part_id'. + +=item $student_table + +The student_table has two columns. The first is a 'student_id' and the second +is the text description of the 'student' (typically username:domain) (less +than 100 characters). The 'student_id' is automatically generated by MySQL. +The use of the name 'student_id' is loaded, I know, but this ID is used ONLY +internally to the MySQL database and is not the same as the students ID +(stored in the students environment). This table has its PRIMARY KEY on the +'student' (100 characters). + +=item $updatetime_table + +The updatetime_table has two columns. The first is 'student' (100 characters, +typically username:domain). The second is 'updatetime', which is an unsigned +integer, NOT a MySQL date. This table has its PRIMARY KEY on 'student' (100 +characters). + +=item $performance_table + +The performance_table has 9 columns. The first three are 'symb_id', +'student_id', and 'part_id'. These comprise the PRIMARY KEY for this table +and are directly related to the $symb_table, $student_table, and $part_table +described above. MySQL does better indexing on numeric items than text, +so we use these three "index tables". The remaining columns are +'solved', 'tries', 'awarded', 'award', 'awarddetail', and 'timestamp'. +These are either the MySQL type TINYTEXT or various integers ('tries' and +'timestamp'). This table has KEYs of 'student_id' and 'symb_id'. +For use of this table, see the functions described below. + +=item $parameters_table + +The parameters_table holds the data that does not fit neatly into the +performance_table. The parameters table has four columns: 'symb_id', +'student_id', 'parameter', and 'value'. 'symb_id', 'student_id', and +'parameter' comprise the PRIMARY KEY for this table. 'parameter' is +limited to 255 characters. 'value' is limited to 64k characters. + +=back + +=head3 Important Subroutines + +Here is a brief overview of the subroutines which are likely to be of +interest: + +=over 4 + +=item &get_current_state(): programmers interface. + +=item &init_dbs(): table creation + +=item &update_student_data(): data storage calls + +=item &get_student_data_from_performance_cache(): data retrieval + +=back + +=head3 Main Documentation + +=over 4 + +=cut + +################################################ +################################################ + +################################################ +################################################ +{ + +my $current_course =''; +my $symb_table; +my $part_table; +my $student_table; +my $updatetime_table; +my $performance_table; +my $parameters_table; + +################################################ +################################################ + +=pod + +=item &setup_table_names() + +input: course id + +output: none + +Sets the package variables for the MySQL table names: + +=over 4 + +=item $symb_table + +=item $part_table + +=item $student_table + +=item $updatetime_table + +=item $performance_table + +=item $parameters_table + +=back + +=cut + +################################################ +################################################ +sub setup_table_names { + my $courseid = shift; + if (! defined($courseid)) { + $courseid = $ENV{'request.course.id'}; + } + # + # Set up database names + my $base_id = $courseid; + $symb_table = $base_id.'_'.'symb'; + $part_table = $base_id.'_'.'part'; + $student_table = $base_id.'_'.'student'; + $updatetime_table = $base_id.'_'.'updatetime'; + $performance_table = $base_id.'_'.'performance'; + $parameters_table = $base_id.'_'.'parameters'; + return; +} + +################################################ +################################################ + +=pod + +=item &init_dbs() + +Input: course id + +Output: 0 on success, positive integer on error + +This routine issues the calls to lonmysql to create the tables used to +store student data. + +=cut + +################################################ +################################################ +sub init_dbs { + my $courseid = shift; + &setup_table_names($courseid); + # + # Note - changes to this table must be reflected in the code that + # stores the data (calls &Apache::lonmysql::store_row with this table + # id + my $symb_table_def = { + id => $symb_table, + permanent => 'no', + columns => [{ name => 'symb_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL', + auto_inc => 'yes', }, + { name => 'symb', + type => 'MEDIUMTEXT', + restrictions => 'NOT NULL'}, + ], + 'PRIMARY KEY' => ['symb_id'], + }; + # + my $part_table_def = { + id => $part_table, + permanent => 'no', + columns => [{ name => 'part_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL', + auto_inc => 'yes', }, + { name => 'part', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL'}, + ], + 'PRIMARY KEY' => ['part (100)'], + 'KEY' => [{ columns => ['part_id']},], + }; + # + my $student_table_def = { + id => $student_table, + permanent => 'no', + columns => [{ name => 'student_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL', + auto_inc => 'yes', }, + { name => 'student', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL'}, + ], + 'PRIMARY KEY' => ['student (100)'], + 'KEY' => [{ columns => ['student_id']},], + }; + # + my $updatetime_table_def = { + id => $updatetime_table, + permanent => 'no', + columns => [{ name => 'student', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL UNIQUE',}, + { name => 'updatetime', + type => 'INT UNSIGNED', + restrictions => 'NOT NULL' }, + ], + 'PRIMARY KEY' => ['student (100)'], + }; + # + my $performance_table_def = { + id => $performance_table, + permanent => 'no', + columns => [{ name => 'symb_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL' }, + { name => 'student_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL' }, + { name => 'part_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL' }, + { name => 'solved', + type => 'TINYTEXT' }, + { name => 'tries', + type => 'SMALLINT UNSIGNED' }, + { name => 'awarded', + type => 'TINYTEXT' }, + { name => 'award', + type => 'TINYTEXT' }, + { name => 'awarddetail', + type => 'TINYTEXT' }, + { name => 'timestamp', + type => 'INT UNSIGNED'}, + ], + 'PRIMARY KEY' => ['symb_id','student_id','part_id'], + 'KEY' => [{ columns=>['student_id'] }, + { columns=>['symb_id'] },], + }; + # + my $parameters_table_def = { + id => $parameters_table, + permanent => 'no', + columns => [{ name => 'symb_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL' }, + { name => 'student_id', + type => 'MEDIUMINT UNSIGNED', + restrictions => 'NOT NULL' }, + { name => 'parameter', + type => 'TINYTEXT', + restrictions => 'NOT NULL' }, + { name => 'value', + type => 'MEDIUMTEXT' }, + ], + 'PRIMARY KEY' => ['symb_id','student_id','parameter (255)'], + }; + # + # Create the tables + my $tableid; + $tableid = &Apache::lonmysql::create_table($symb_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating symb_table: ". + &Apache::lonmysql::get_error()); + return 1; + } + # + $tableid = &Apache::lonmysql::create_table($part_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating part_table: ". + &Apache::lonmysql::get_error()); + return 2; + } + # + $tableid = &Apache::lonmysql::create_table($student_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating student_table: ". + &Apache::lonmysql::get_error()); + return 3; + } + # + $tableid = &Apache::lonmysql::create_table($updatetime_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating updatetime_table: ". + &Apache::lonmysql::get_error()); + return 4; + } + # + $tableid = &Apache::lonmysql::create_table($performance_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating preformance_table: ". + &Apache::lonmysql::get_error()); + return 5; + } + # + $tableid = &Apache::lonmysql::create_table($parameters_table_def); + if (! defined($tableid)) { + &Apache::lonnet::logthis("error creating parameters_table: ". + &Apache::lonmysql::get_error()); + return 6; + } + return 0; +} + +################################################ +################################################ + +=pod + +=item &get_part_id() + +Get the MySQL id of a problem part string. + +Input: $part + +Output: undef on error, integer $part_id on success. + +=item &get_part() + +Get the string describing a part from the MySQL id of the problem part. + +Input: $part_id + +Output: undef on error, $part string on success. + +=cut + +################################################ +################################################ + +my %ids_by_part; +my %parts_by_id; + +sub get_part_id { + my ($part) = @_; + if (! exists($ids_by_part{$part})) { + &Apache::lonmysql::store_row($part_table,[undef,$part]); + undef(%ids_by_part); + my @Result = &Apache::lonmysql::get_rows($part_table); + foreach (@Result) { + $ids_by_part{$_->[1]}=$_->[0]; + } + } + return $ids_by_part{$part} if (exists($ids_by_part{$part})); + return undef; # error +} + +sub get_part { + my ($part_id) = @_; + if (! exists($parts_by_id{$part_id}) || + ! defined($parts_by_id{$part_id}) || + $parts_by_id{$part_id} eq '') { + my @Result = &Apache::lonmysql::get_rows($part_table); + foreach (@Result) { + $parts_by_id{$_->[0]}=$_->[1]; + } + } + return $parts_by_id{$part_id} if(exists($parts_by_id{$part_id})); + return undef; # error +} + +################################################ +################################################ + +=pod + +=item &get_symb_id() + +Get the MySQL id of a symb. + +Input: $symb + +Output: undef on error, integer $symb_id on success. + +=item &get_symb() + +Get the symb associated with a MySQL symb_id. + +Input: $symb_id + +Output: undef on error, $symb on success. + +=cut + +################################################ +################################################ + +my %ids_by_symb; +my %symbs_by_id; + +sub get_symb_id { + my ($symb) = @_; + if (! exists($ids_by_symb{$symb})) { + &Apache::lonmysql::store_row($symb_table,[undef,$symb]); + undef(%ids_by_symb); + my @Result = &Apache::lonmysql::get_rows($symb_table); + foreach (@Result) { + $ids_by_symb{$_->[1]}=$_->[0]; + } + } + return $ids_by_symb{$symb} if(exists( $ids_by_symb{$symb})); + return undef; # error +} + +sub get_symb { + my ($symb_id) = @_; + if (! exists($symbs_by_id{$symb_id}) || + ! defined($symbs_by_id{$symb_id}) || + $symbs_by_id{$symb_id} eq '') { + my @Result = &Apache::lonmysql::get_rows($symb_table); + foreach (@Result) { + $symbs_by_id{$_->[0]}=$_->[1]; + } + } + return $symbs_by_id{$symb_id} if(exists( $symbs_by_id{$symb_id})); + return undef; # error +} + +################################################ +################################################ + +=pod + +=item &get_student_id() + +Get the MySQL id of a student. + +Input: $sname, $dom + +Output: undef on error, integer $student_id on success. + +=item &get_student() + +Get student username:domain associated with the MySQL student_id. + +Input: $student_id + +Output: undef on error, string $student (username:domain) on success. + +=cut + +################################################ +################################################ + +my %ids_by_student; +my %students_by_id; + +sub get_student_id { + my ($sname,$sdom) = @_; + my $student = $sname.':'.$sdom; + if (! exists($ids_by_student{$student})) { + &Apache::lonmysql::store_row($student_table,[undef,$student]); + undef(%ids_by_student); + my @Result = &Apache::lonmysql::get_rows($student_table); + foreach (@Result) { + $ids_by_student{$_->[1]}=$_->[0]; + } + } + return $ids_by_student{$student} if(exists( $ids_by_student{$student})); + return undef; # error +} + +sub get_student { + my ($student_id) = @_; + if (! exists($students_by_id{$student_id}) || + ! defined($students_by_id{$student_id}) || + $students_by_id{$student_id} eq '') { + my @Result = &Apache::lonmysql::get_rows($student_table); + foreach (@Result) { + $students_by_id{$_->[0]}=$_->[1]; + } + } + return $students_by_id{$student_id} if(exists($students_by_id{$student_id})); + return undef; # error +} + +################################################ +################################################ + +=pod + +=item &update_student_data() + +Input: $sname, $sdom, $courseid + +Output: $returnstatus, \%student_data + +$returnstatus is a string describing any errors that occured. 'okay' is the +default. +\%student_data is the data returned by a call to lonnet::currentdump. + +This subroutine loads a students data using lonnet::currentdump and inserts +it into the MySQL database. The inserts are done on two tables, +$performance_table and $parameters_table. $parameters_table holds the data +that is not included in $performance_table. See the description of +$performance_table elsewhere in this file. The INSERT calls are made +directly by this subroutine, not through lonmysql because we do a 'bulk' +insert which takes advantage of MySQLs non-SQL compliant INSERT command to +insert multiple rows at a time. If anything has gone wrong during this +process, $returnstatus is updated with a description of the error and +\%student_data is returned. + +Notice we do not insert the data and immediately query it. This means it +is possible for there to be data returned this first time that is not +available the second time. CYA. + +=cut + +################################################ +################################################ +sub update_student_data { + my ($sname,$sdom,$courseid) = @_; + # + my $student_id = &get_student_id($sname,$sdom); + my $student = $sname.':'.$sdom; + # + my $returnstatus = 'okay'; + # + # Set up database names + &setup_table_names($courseid); + # + # Download students 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]); + $returnstatus = 'error getting data'; + return $returnstatus; + } + if (scalar(@tmp) < 1) { + return ('no data',undef); + } + my %student_data = @tmp; + # + # Remove all of the students data from the table + &Apache::lonmysql::remove_from_table($performance_table,'student_id', + $student_id); + # + # Store away the data + # + my $starttime = Time::HiRes::time; + my $elapsed = 0; + my $rows_stored; + my $store_parameters_command = 'INSERT INTO '.$parameters_table. + ' VALUES '; + my $store_performance_command = 'INSERT INTO '.$performance_table. + ' VALUES '; + my $dbh = &Apache::lonmysql::get_dbh(); + return 'error' if (! defined($dbh)); + while (my ($current_symb,$param_hash) = each(%student_data)) { + # + # make sure the symb is set up properly + my $symb_id = &get_symb_id($current_symb); + # + # Load data into the tables + while (my ($parameter,$value) = each (%$param_hash)) { + my $newstring; + if ($parameter !~ /(timestamp|resource\.(.*)\.(solved|tries|awarded|award|awarddetail|previous))/) { + $newstring = "('".join("','", + $symb_id,$student_id, + $parameter,$value)."'),"; + if ($newstring !~ /''/) { + $store_parameters_command .= $newstring; + $rows_stored++; + } + } + next if ($parameter !~ /^resource\.(.*)\.solved$/); + # + my $part = $1; + my $part_id = &get_part_id($part); + next if (!defined($part_id)); + my $solved = $value; + my $tries = $param_hash->{'resource.'.$part.'.tries'}; + my $awarded = $param_hash->{'resource.'.$part.'.awarded'}; + my $award = $param_hash->{'resource.'.$part.'.award'}; + my $awarddetail = $param_hash->{'resource.'.$part.'.awarddetail'}; + my $timestamp = $param_hash->{'timestamp'}; + $solved = '' if (! defined($awarded)); + $tries = '' if (! defined($tries)); + $awarded = '' if (! defined($awarded)); + $award = '' if (! defined($award)); + $awarddetail = '' if (! defined($awarddetail)); + $newstring = "('".join("','",$symb_id,$student_id,$part_id, + $solved,$tries,$awarded,$award, + $awarddetail,$timestamp)."'),"; + $store_performance_command .= $newstring; + $rows_stored++; + } + } + chop $store_parameters_command; + chop $store_performance_command; + my $start = Time::HiRes::time; + $dbh->do($store_parameters_command); + if ($dbh->err()) { + &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr()); + &Apache::lonnet::logthis('command = '.$store_performance_command); + $returnstatus = 'error: unable to insert parameters into database'; + return $returnstatus,\%student_data; + } + $dbh->do($store_performance_command); + if ($dbh->err()) { + &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr()); + &Apache::lonnet::logthis('command = '.$store_parameters_command); + $returnstatus = 'error: unable to insert performance into database'; + return $returnstatus,\%student_data; + } + $elapsed += Time::HiRes::time - $start; + # + # Set the students update time + &Apache::lonmysql::replace_row($updatetime_table, + [$student,$time_of_retrieval]); + &Apache::lonnet::logthis('store took: '.(Time::HiRes::time - $starttime).' for '.$rows_stored); + &Apache::lonnet::logthis('mysql store took: '.$elapsed.' for '.$rows_stored); + return ($returnstatus,\%student_data); +} + +################################################ +################################################ + +=pod + +=item &ensure_current_data() + +Input: $sname, $sdom, $courseid + +Output: $status, $data + +This routine ensures the data for a given student is up to date. It calls +&init_dbs() if the tables do not exist. The $updatetime_table is queried +to determine the time of the last update. If the students data is out of +date, &update_student_data() is called. The return values from the call +to &update_student_data() are returned. + +=cut + +################################################ +################################################ +sub ensure_current_data { + my ($sname,$sdom,$courseid) = @_; + my $status = 'okay'; # return value + # + &setup_table_names($courseid); + # + # if the tables do not exist, make them + my @CurrentTable = &Apache::lonmysql::tables_in_db(); + my ($found_symb,$found_student,$found_part,$found_update, + $found_performance,$found_parameters); + foreach (@CurrentTable) { + $found_symb = 1 if ($_ eq $symb_table); + $found_student = 1 if ($_ eq $student_table); + $found_part = 1 if ($_ eq $part_table); + $found_update = 1 if ($_ eq $updatetime_table); + $found_performance = 1 if ($_ eq $performance_table); + $found_parameters = 1 if ($_ eq $parameters_table); + } + if (!$found_symb || !$found_update || + !$found_student || !$found_part || + !$found_performance || !$found_parameters) { + if (&init_dbs($courseid)) { + return 'error'; + } + } + # + # Get the update time for the user + my $updatetime = 0; + my $modifiedtime = 1; + # + my $student = $sname.':'.$sdom; + my @Result = &Apache::lonmysql::get_rows($updatetime_table, + "student ='$student'"); + my $data = undef; + if (@Result) { + $updatetime = $Result[0]->[1]; + } + if ($modifiedtime > $updatetime) { + ($status,$data) = &update_student_data($sname,$sdom,$courseid); + } + return ($status,$data); +} + +################################################ +################################################ + +=pod + +=item &get_student_data_from_performance_cache() + +Input: $sname, $sdom, $symb, $courseid + +Output: hash reference containing the data for the given student. +If $symb is undef, all the students data is returned. + +This routine is the heart of the local caching system. See the description +of $performance_table, $symb_table, $student_table, and $part_table. The +main task is building the MySQL request. The tables appear in the request +in the order in which they should be parsed by MySQL. When searching +on a student the $student_table is used to locate the 'student_id'. All +rows in $performance_table which have a matching 'student_id' are returned, +with data from $part_table and $symb_table which match the entries in +$performance_table, 'part_id' and 'symb_id'. When searching on a symb, +the $symb_table is processed first, with matching rows grabbed from +$performance_table and filled in from $part_table and $student_table in +that order. + +Running 'EXPLAIN ' on the 'SELECT' statements generated can be quite +interesting, especially if you play with the order the tables are listed. + +=cut + +################################################ +################################################ +sub get_student_data_from_performance_cache { + my ($sname,$sdom,$symb,$courseid)=@_; + my $student = $sname.':'.$sdom if (defined($sname) && defined($sdom)); + &setup_table_names(); + # + # Return hash + my $studentdata; + # + my $dbh = &Apache::lonmysql::get_dbh(); + my $request = "SELECT ". + "d.symb,c.part,a.solved,a.tries,a.awarded,a.award,a.awarddetail,". + "a.timestamp "; + if (defined($student)) { + $request .= "FROM $student_table AS b ". + "LEFT JOIN $performance_table AS a ON b.student_id=a.student_id ". + "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". + "LEFT JOIN $symb_table AS d ON d.symb_id = a.symb_id ". + "WHERE student='$student'"; + if (defined($symb) && $symb ne '') { + $request .= " AND d.symb='".$dbh->quote($symb)."'"; + } + } elsif (defined($symb) && $symb ne '') { + $request .= "FROM $symb_table as d ". + "LEFT JOIN $performance_table AS a ON d.symb_id=a.symb_id ". + "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". + "LEFT JOIN $student_table AS b ON b.student_id = a.student_id ". + "WHERE symb='".$dbh->quote($symb)."'"; + } + my $starttime = Time::HiRes::time; + my $rows_retrieved = 0; + my $sth = $dbh->prepare($request); + $sth->execute(); + if ($sth->err()) { + &Apache::lonnet::logthis("Unable to execute MySQL request:"); + &Apache::lonnet::logthis("\n".$request."\n"); + &Apache::lonnet::logthis("error is:".$sth->errstr()); + return undef; + } + foreach my $row (@{$sth->fetchall_arrayref}) { + $rows_retrieved++; + my ($symb,$part,$solved,$tries,$awarded,$award,$awarddetail,$time) = + (@$row); + my $base = 'resource.'.$part; + $studentdata->{$symb}->{$base.'.solved'} = $solved; + $studentdata->{$symb}->{$base.'.tries'} = $tries; + $studentdata->{$symb}->{$base.'.awarded'} = $awarded; + $studentdata->{$symb}->{$base.'.award'} = $award; + $studentdata->{$symb}->{$base.'.awarddetail'} = $awarddetail; + $studentdata->{$symb}->{'timestamp'} = $time if (defined($time) && $time ne ''); + } + &Apache::lonnet::logthis('retrieve took: '.(Time::HiRes::time - $starttime).' for '.$rows_retrieved); + return $studentdata; +} + +################################################ +################################################ + +=pod + +=item &get_current_state() + +Input: $sname,$sdom,$symb,$courseid + +Output: Described below Retrieve the current status of a students performance. $sname and $sdom are the only required parameters. If $symb is undef the results @@ -1567,7 +2391,7 @@ If $symb is specified, a hash of ) is returned. -If no data is found for $symb, or if the student has not performance data, +If no data is found for $symb, or if the student has no performance data, an empty list is returned. =cut @@ -1576,98 +2400,36 @@ an empty list is returned. ################################################ sub get_current_state { my ($sname,$sdom,$symb,$courseid,$forcedownload)=@_; + if ($current_course ne $courseid) { + # Clear out variables + undef(%ids_by_part); + undef(%parts_by_id); + undef(%ids_by_symb); + undef(%symbs_by_id); + undef(%ids_by_student); + undef(%students_by_id); + $current_course = $courseid; + } 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 $cachefilename = $Apache::lonnet::tmpdir.$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_'. - $courseid.'_'. - $sname.'_'.$sdom. - '_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'}; - } - untie(%cache); - } - # timestamp/devalidation check should go here. - my $modifiedtime = 1; - # Take whatever steps are neccessary at this point to give $modifiedtime a - # new value + my ($status,$data) = &ensure_current_data($sname,$sdom,$courseid); # - if (($updatetime < $modifiedtime) || - (defined($forcedownload) && $forcedownload)) { - # 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)) { - 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); - } + if (defined($data)) { + return %$data; + } elsif ($status eq 'no data') { + return (); } else { - 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'); - $student_data{&Apache::lonnet::unescape($tmpsymb)} = - &make_into_hash($params); - } - } - } - untie(%cache); + if ($status ne 'okay' && $status ne '') { + &Apache::lonnet::logthis('status = '.$status); + return (); } + my $returnhash = &get_student_data_from_performance_cache($sname,$sdom, + $symb,$courseid); + return %$returnhash if (defined($returnhash)); } - if (! defined($symb)) { - return %student_data; - } elsif (exists($student_data{$symb})) { - return %{$student_data{$symb}}; - } else { - return (); - } + return (); } ################################################ @@ -1675,6 +2437,24 @@ sub get_current_state { =pod +=back + +=item End of Local Data Caching Subroutines + +=cut + +################################################ +################################################ + + +} +################################################ +################################################ + +=pod + +=head3 Classlist Subroutines + =item &get_classlist(); Retrieve the classist of a given class or of the current class. Student @@ -1713,7 +2493,7 @@ sub get_classlist { $cid = $cid || $ENV{'request.course.id'}; $cdom = $cdom || $ENV{'course.'.$cid.'.domain'}; $cnum = $cnum || $ENV{'course.'.$cid.'.num'}; - my $now = time; + my $now = time; # my %classlist=&Apache::lonnet::dump('classlist',$cdom,$cnum); while (my ($student,$info) = each(%classlist)) {