Diff for /loncom/metadata_database/parse_activity_log.pl between versions 1.13 and 1.19

version 1.13, 2005/02/09 21:54:15 version 1.19, 2005/09/20 17:49:51
Line 61  use Getopt::Long(); Line 61  use Getopt::Long();
 use IO::File;  use IO::File;
 use File::Copy;  use File::Copy;
 use Fcntl qw(:flock);  use Fcntl qw(:flock);
   use HTML::TokeParser;
   
 #  #
 # Determine parameters  # Determine parameters
 my ($help,$course,$domain,$drop_when_done,$srcfile,$logfile,$time_run,$nocleanup,$log,$backup);  my ($help,$course,$domain,$drop_when_done,$srcfile,$logfile,$time_run,$nocleanup,$log,$backup,$xmlfile);
 &Getopt::Long::GetOptions( "course=s"  => \$course,  &Getopt::Long::GetOptions( "course=s"  => \$course,
                            "domain=s"  => \$domain,                             "domain=s"  => \$domain,
                            "backup"    => \$backup,                             "backup"    => \$backup,
                            "help"      => \$help,                             "help"      => \$help,
                            "logfile=s" => \$logfile,                             "logfile=s" => \$logfile,
                            "srcfile=s" => \$srcfile,                             "srcfile=s" => \$srcfile,
                              "justloadxml=s" => \$xmlfile,
                            "timerun"   => \$time_run,                             "timerun"   => \$time_run,
                            "nocleanup" => \$nocleanup,                             "nocleanup" => \$nocleanup,
                            "dropwhendone" => \$drop_when_done,                             "dropwhendone" => \$drop_when_done,
Line 129  if ($log) { Line 131  if ($log) {
     print STDERR "$0: logging to $logfile".$/;      print STDERR "$0: logging to $logfile".$/;
     if (! open(LOGFILE,">$logfile")) {      if (! open(LOGFILE,">$logfile")) {
         warn("Unable to open $logfile for writing.  Run aborted.");          warn("Unable to open $logfile for writing.  Run aborted.");
         exit 5;          &clean_up_and_exit(5);
     } else {      } else {
         $logthis = \&log_to_file;          $logthis = \&log_to_file;
     }      }
Line 141  if ($log) { Line 143  if ($log) {
 ##  ##
 my $sourcefilename;   # activity log data  my $sourcefilename;   # activity log data
 my $newfilename;      # $sourcefilename will be renamed to this  my $newfilename;      # $sourcefilename will be renamed to this
 my $gz_sql_filename;  # the gzipped mysql backup data file name.  
 my $error_filename;   # Errors in parsing the activity log will be written here  my $error_filename;   # Errors in parsing the activity log will be written here
   my $chunk_filename;   # where we save data we are not going to write to db
 if ($srcfile) {  if ($srcfile) {
     $sourcefilename = $srcfile;      $sourcefilename = $srcfile;
 } else {  } else {
Line 150  if ($srcfile) { Line 152  if ($srcfile) {
 }  }
 my $sql_filename = $sourcefilename;  my $sql_filename = $sourcefilename;
 $sql_filename =~ s|[^/]*$|activity.log.sql|;  $sql_filename =~ s|[^/]*$|activity.log.sql|;
 $gz_sql_filename = $sql_filename.'.gz';  my $gz_sql_filename = $sql_filename.'.gz';
   #
   $chunk_filename = $sourcefilename.".unprocessed_chunks";
   #
   my $xml_filename = $sourcefilename;
   my $gz_xml_filename = $xml_filename.'.gz';
   if (defined($xmlfile)) {
       $xml_filename = $xmlfile;
       if ($xml_filename =~ /\.gz$/) {
           $gz_xml_filename = $xml_filename;
       } else {
           $gz_xml_filename = $xml_filename.'.gz';
       }
   } else {
       my $xml_filename = $sourcefilename;
       $xml_filename =~ s|[^/]*$|activity.log.xml|;
       $gz_xml_filename = $xml_filename.'.gz';
   }
   #
 $error_filename = $sourcefilename;  $error_filename = $sourcefilename;
 $error_filename =~ s|[^/]*$|activity.log.errors|;  $error_filename =~ s|[^/]*$|activity.log.errors|;
 $logthis->('Beginning logging '.time);  $logthis->('Beginning logging '.time);
   
   
 #  #
 # Wait for a lock on the lockfile to avoid collisions  # Wait for a lock on the lockfile to avoid collisions
 my $lockfilename = $sourcefilename.'.lock';  my $lockfilename = $sourcefilename.'.lock';
 open(LOCKFILE,'>'.$lockfilename);  $newfilename = $sourcefilename.'.processing';
 if (!flock(LOCKFILE,LOCK_EX)) {  if (! defined($xmlfile)) {
     warn("Unable to lock $lockfilename.  Aborting".$/);      open(LOCKFILE,'>'.$lockfilename);
     exit 6;      if (!flock(LOCKFILE,LOCK_EX|LOCK_NB)) {
 }          warn("Unable to lock $lockfilename.  Aborting".$/);
           # don't call clean_up_and_exit another instance is running and
 ##          # we don't want to 'cleanup' there files
 ## There will only be a $newfilename file if a copy of this program is already          exit 6;
 ## running.      }
 my $newfilename = $sourcefilename.'.processing';  
 if (-e $newfilename) {      if (! -e $newfilename && -e $sourcefilename) {
     warn "$newfilename exists";          $logthis->('renaming '.$sourcefilename.' to '.$newfilename);
     $logthis->($newfilename.' exists, so I cannot work on it.');          rename($sourcefilename,$newfilename);
     exit 2;          Copy($newfilename,$newfilename.'.'.time) if ($backup);
 }          $logthis->("renamed $sourcefilename to $newfilename");
       } elsif (! -e $newfilename) {
 if (-e $sourcefilename) {          utime(undef,undef,$newfilename);
     $logthis->('renaming '.$sourcefilename.' to '.$newfilename);      }
     rename($sourcefilename,$newfilename);  
     Copy($newfilename,$newfilename.'.'.time) if ($backup);  
     $logthis->("renamed $sourcefilename to $newfilename");  
 } else {  
     my $command = 'touch '.$newfilename;  
     $logthis->($command);  
     system($command);  
     $logthis->('touch was completed');  
 }  }
   
 close(LOCKFILE);  
   
 ##  ##
 ## Table definitions  ## Table definitions
 ##  ##
 my $prefix = $course.'_'.$domain.'_';  my %tables = &table_names($course,$domain);
 my $student_table = &Apache::lonmysql::fix_table_name($prefix.'students');  
 my $student_table_def =   my $student_table_def = 
 { id => $student_table,  { id => $tables{'student'},
   permanent => 'no',    permanent => 'no',
   columns => [    columns => [
               { name => 'student_id',                { name => 'student_id',
Line 209  my $student_table_def = Line 217  my $student_table_def =
       'PRIMARY KEY' => ['student_id',],        'PRIMARY KEY' => ['student_id',],
           };            };
   
 my $res_table = &Apache::lonmysql::fix_table_name($prefix.'resource');  
 my $res_table_def =   my $res_table_def = 
 { id => $res_table,  { id => $tables{'res'},
   permanent => 'no',    permanent => 'no',
   columns => [{ name => 'res_id',    columns => [{ name => 'res_id',
                 type => 'MEDIUMINT UNSIGNED',                  type => 'MEDIUMINT UNSIGNED',
Line 224  my $res_table_def = Line 231  my $res_table_def =
   'PRIMARY KEY' => ['res_id'],    'PRIMARY KEY' => ['res_id'],
 };  };
   
 #my $action_table = &Apache::lonmysql::fix_table_name($prefix.'actions');  
 #my $action_table_def =  #my $action_table_def =
 #{ id => $action_table,  #{ id => $action_table,
 #  permanent => 'no',  #  permanent => 'no',
Line 239  my $res_table_def = Line 245  my $res_table_def =
 #  'PRIMARY KEY' => ['action_id',],   #  'PRIMARY KEY' => ['action_id',], 
 #};  #};
   
 my $machine_table = &Apache::lonmysql::fix_table_name($prefix.'machine_table');  
 my $machine_table_def =  my $machine_table_def =
 { id => $machine_table,  { id => $tables{'machine'},
   permanent => 'no',    permanent => 'no',
   columns => [{ name => 'machine_id',    columns => [{ name => 'machine_id',
                 type => 'MEDIUMINT UNSIGNED',                  type => 'MEDIUMINT UNSIGNED',
Line 254  my $machine_table_def = Line 259  my $machine_table_def =
   'PRIMARY KEY' => ['machine_id',],    'PRIMARY KEY' => ['machine_id',],
  };   };
   
 my $activity_table = &Apache::lonmysql::fix_table_name($prefix.'activity');  
 my $activity_table_def =   my $activity_table_def = 
 { id => $activity_table,  { id => $tables{'activity'},
   permanent => 'no',    permanent => 'no',
   columns => [    columns => [
               { name => 'res_id',                { name => 'res_id',
Line 288  my $activity_table_def = Line 292  my $activity_table_def =
   
 my @Activity_Table = ($activity_table_def);  my @Activity_Table = ($activity_table_def);
 my @ID_Tables = ($student_table_def,$res_table_def,$machine_table_def);  my @ID_Tables = ($student_table_def,$res_table_def,$machine_table_def);
                  
 ##  ##
 ## End of table definitions  ## End of table definitions
 ##  ##
   $logthis->('tables = '.join(',',keys(%tables)));
   
 $logthis->('Connectiong to mysql');  $logthis->('Connectiong to mysql');
 &Apache::lonmysql::set_mysql_user_and_password('www',  &Apache::lonmysql::set_mysql_user_and_password('www',
Line 298  $logthis->('Connectiong to mysql'); Line 304  $logthis->('Connectiong to mysql');
 if (!&Apache::lonmysql::verify_sql_connection()) {  if (!&Apache::lonmysql::verify_sql_connection()) {
     warn "Unable to connect to MySQL database.";      warn "Unable to connect to MySQL database.";
     $logthis->("Unable to connect to MySQL database.");      $logthis->("Unable to connect to MySQL database.");
     exit 3;      &clean_up_and_exit(3);
 }  }
 $logthis->('SQL connection is up');  $logthis->('SQL connection is up');
   
 if (-s $gz_sql_filename) {  my $missing_table = &check_for_missing_tables(values(%tables));
   if (-s $gz_sql_filename && ! -s $gz_xml_filename) {
     my $backup_modification_time = (stat($gz_sql_filename))[9];      my $backup_modification_time = (stat($gz_sql_filename))[9];
     $logthis->($gz_sql_filename.' was last modified '.      $logthis->($gz_sql_filename.' was last modified '.
                localtime($backup_modification_time).                 localtime($backup_modification_time).
                '('.$backup_modification_time.')');                 '('.$backup_modification_time.')');
     # Check for missing tables      if ($missing_table) {
     my @Current_Tables = &Apache::lonmysql::tables_in_db();          # If the backup happened prior to the last table modification,
     $logthis->(join(',',@Current_Tables));          # we need to save the tables.
     my %Found;          if (&latest_table_modification_time() > $backup_modification_time) {
     foreach my $tablename (@Current_Tables) {              # Save the current tables in case we need them another time.
         foreach my $table (@Activity_Table,@ID_Tables) {              $logthis->('Backing existing tables up');
             if ($tablename eq  $table->{'id'}) {              &backup_tables_as_xml($gz_xml_filename.'.save_'.time,\%tables);
                 $Found{$tablename}++;  
             }  
         }  
     }  
     $logthis->('Found tables '.join(',',keys(%Found)));  
     my $missing_a_table = 0;  
     foreach my $table (@Activity_Table,@ID_Tables) {      
         # Hmmm, should I dump the tables?  
         if (! $Found{$table->{'id'}}) {  
             $logthis->('Missing table '.$table->{'id'});  
             $missing_a_table = 1;  
             last;  
         }          }
           $time_this->();
           &load_backup_sql_tables($gz_sql_filename);
           &backup_tables_as_xml($gz_xml_filename,\%tables);
           $time_this->('load backup tables');
     }      }
     if ($missing_a_table) {  } elsif (-s $gz_xml_filename) {
       my $backup_modification_time = (stat($gz_xml_filename))[9];
       $logthis->($gz_xml_filename.' was last modified '.
                  localtime($backup_modification_time).
                  '('.$backup_modification_time.')');
       if ($missing_table) {
         my $table_modification_time = $backup_modification_time;          my $table_modification_time = $backup_modification_time;
         # If the backup happened prior to the last table modification,          # If the backup happened prior to the last table modification,
         foreach my $table (@Activity_Table,@ID_Tables) {              # we need to save the tables.
             my %tabledata = &Apache::lonmysql::table_information($table->{'id'});          if (&latest_table_modification_time() > $backup_modification_time) {
             next if (! scalar(keys(%tabledata))); # table does not exist  
             if ($table_modification_time < $tabledata{'Update_time'}) {  
                 $table_modification_time = $tabledata{'Update_time'};  
             }  
         }  
         $logthis->("Table modification time = ".$table_modification_time);  
         if ($table_modification_time > $backup_modification_time) {  
             # Save the current tables in case we need them another time.              # Save the current tables in case we need them another time.
             my $backup_name = $gz_sql_filename.'.'.time;              $logthis->('Backing existing tables up');
             $logthis->('Backing existing tables up in '.$backup_name);              &backup_tables_as_xml($gz_xml_filename.'.save_'.time,\%tables);
             &backup_tables($backup_name);  
         }          }
         $time_this->();          $time_this->();
         &load_backup_tables($gz_sql_filename);          # We have to make our own tables for the xml format
           &drop_tables();
           &create_tables();
           &load_backup_xml_tables($gz_xml_filename,\%tables);
         $time_this->('load backup tables');          $time_this->('load backup tables');
     }      }    
   }
   
   if (defined($xmlfile)) {
       &clean_up_and_exit(0);
 }  }
   
 ##  ##
Line 358  $logthis->('creating tables'); Line 361  $logthis->('creating tables');
 if (! &create_tables()) {  if (! &create_tables()) {
     warn "Unable to create tables";      warn "Unable to create tables";
     $logthis->('Unable to create tables');      $logthis->('Unable to create tables');
     exit 4;      &clean_up_and_exit(4);
 }  }
   
 ##  ##
Line 375  my $error_fh = IO::File->new(">>$error_f Line 378  my $error_fh = IO::File->new(">>$error_f
 ## Parse the course log  ## Parse the course log
 $logthis->('processing course log');  $logthis->('processing course log');
 if (-s $newfilename) {  if (-s $newfilename) {
     my $result = &process_courselog($newfilename,$error_fh);      my $result = &process_courselog($newfilename,$error_fh,\%tables);
     if (! defined($result)) {      if (! defined($result)) {
         # Something went wrong along the way...          # Something went wrong along the way...
         $logthis->('process_courselog returned undef');          $logthis->('process_courselog returned undef');
         exit 5;          &clean_up_and_exit(5);
     } elsif ($result > 0) {      } elsif ($result > 0) {
         $time_this->();          $time_this->();
         $logthis->('process_courselog returned '.$result.' backing up tables');          $logthis->('process_courselog returned '.$result.'.'.$/.
         &backup_tables($gz_sql_filename);                     'Backing up tables');
           &backup_tables_as_xml($gz_xml_filename,\%tables);
         $time_this->('write backup tables');          $time_this->('write backup tables');
     }      }
     if ($drop_when_done) { &drop_tables(); $logthis->('dropped tables'); }      if ($drop_when_done) { &drop_tables(); $logthis->('dropped tables'); }
Line 406  if ($time_run) { Line 410  if ($time_run) {
     $logthis->(&outputtimes());      $logthis->(&outputtimes());
 }  }
   
 if ($log) {  &clean_up_and_exit(0);
     close LOGFILE;  
 }  
   
 foreach my $file ($lockfilename, $error_filename,$logfile) {  ########################################################
     if (-z $file) {   ########################################################
         unlink($file);   
   sub clean_up_and_exit {
       my ($exit_code) = @_;
       # Close files
       close(LOCKFILE);
       close(LOGFILE);
       # Remove zero length files
       foreach my $file ($lockfilename, $error_filename,$logfile) {
           if (defined($file) && -z $file) { 
               unlink($file); 
           }
     }      }
 }  
   
       exit $exit_code;
   }
   
 exit 0;   # Everything is okay, so end here before it gets worse.  ########################################################
   ########################################################
   sub table_names {
       my ($course,$domain) = @_;
       my $prefix = $course.'_'.$domain.'_';
       #
       my %tables = 
           ( student =>&Apache::lonmysql::fix_table_name($prefix.'students'),
             res     =>&Apache::lonmysql::fix_table_name($prefix.'resource'),
             machine =>&Apache::lonmysql::fix_table_name($prefix.'machine_table'),
             activity=>&Apache::lonmysql::fix_table_name($prefix.'activity'),
             );
       return %tables;
   }
   
 ########################################################  ########################################################
 ########################################################  ########################################################
Line 429  exit 0;   # Everything is okay, so end h Line 455  exit 0;   # Everything is okay, so end h
 #  #
 # Returns the number of lines in the activity.log file that were processed.  # Returns the number of lines in the activity.log file that were processed.
 sub process_courselog {  sub process_courselog {
     my ($inputfile,$error_fh) = @_;      my ($inputfile,$error_fh,$tables) = @_;
     if (! open(IN,$inputfile)) {      if (! open(IN,$inputfile)) {
         warn "Unable to open '$inputfile' for reading";          warn "Unable to open '$inputfile' for reading";
         $logthis->("Unable to open '$inputfile' for reading");          $logthis->("Unable to open '$inputfile' for reading");
Line 438  sub process_courselog { Line 464  sub process_courselog {
     my ($linecount,$insertcount);      my ($linecount,$insertcount);
     my $dbh = &Apache::lonmysql::get_dbh();      my $dbh = &Apache::lonmysql::get_dbh();
     #      #
     # Timing variables      &store_entry();
     my @RowData;  
     while (my $line=<IN>){      while (my $line=<IN>){
         # last if ($linecount > 1000);          # last if ($linecount > 1000);
         #          #
Line 449  sub process_courselog { Line 474  sub process_courselog {
         $linecount++;          $linecount++;
         # print $linecount++.$/;          # print $linecount++.$/;
         my ($timestamp,$host,$log)=split(/\:/,$line,3);          my ($timestamp,$host,$log)=split(/\:/,$line,3);
         $time_this->('splitline');  
         #          #
         # $log has the actual log entries; currently still escaped, and          # $log has the actual log entries; currently still escaped, and
         # %26(timestamp)%3a(url)%3a(user)%3a(domain)          # %26(timestamp)%3a(url)%3a(user)%3a(domain)
Line 461  sub process_courselog { Line 485  sub process_courselog {
         # get delimiter between timestamped entries to be &&&          # get delimiter between timestamped entries to be &&&
         $log=~s/\%26(\d{9,10})\%3a/\&\&\&$1\%3a/g;          $log=~s/\%26(\d{9,10})\%3a/\&\&\&$1\%3a/g;
         $log = &unescape($log);          $log = &unescape($log);
         $time_this->('translate_and_unescape');  
         # now go over all log entries           # now go over all log entries 
         if (! defined($host)) { $host = 'unknown'; }          if (! defined($host)) { $host = 'unknown'; }
         my $machine_id = &get_id($machine_table,'machine',$host);  
         my $prevchunk = 'none';          my $prevchunk = 'none';
         foreach my $chunk (split(/\&\&\&/,$log)) {          foreach my $chunk (split(/\&\&\&/,$log)) {
               if (length($chunk) > 20000) {
                   # avoid putting too much data into the database
                   # (usually an uploaded file or something similar)
                   $chunk = &escape($chunk);
                   if (! open(CHUNKFILE,">>$chunk_filename") ||
                       ! print CHUNKFILE $timestamp.':'.$host.':'.$chunk.$/) {
                       # abort
                       close(CHUNKFILE);
                       close(IN);
                       return undef;
                   }
                   close(CHUNKFILE);
                   next;
               }
             my $warningflag = '';              my $warningflag = '';
             $time_this->();  
     my ($time,$res,$uname,$udom,$action,@values)= split(/:/,$chunk);      my ($time,$res,$uname,$udom,$action,@values)= split(/:/,$chunk);
             my $student = $uname.':'.$udom;              #
             if (! defined($res) || $res =~ /^\s*$/) {              if (! defined($res) || $res =~ /^\s*$/) {
                 $res = '/adm/roles';                  $res = '/adm/roles';
                 $action = 'LOGIN';                  $action = 'LOGIN';
Line 495  sub process_courselog { Line 530  sub process_courselog {
                          '@values = '.join('&',@values));                           '@values = '.join('&',@values));
                 next; #skip it if we cannot understand what is happening.                  next; #skip it if we cannot understand what is happening.
             }              }
             if (! defined($student) || $student eq ':') {  
                 $student = 'unknown';  
                 $warningflag .= 'student';  
             }  
             if (! defined($res) || $res =~ /^\s*$/) {  
                 $res = 'unknown';  
                 $warningflag .= 'res';  
             }  
             if (! defined($action) || $action =~ /^\s*$/) {  
                 $action = 'unknown';  
                 $warningflag .= 'action';  
             }  
             if (! defined($time) || $time !~ /^\d+$/) {  
                 $time = 0;  
                 $warningflag .= 'time';  
             }  
             #              #
             $time_this->('split_and_error_check');              my %data = (student  => $uname.':'.$udom,
             my $student_id = &get_id($student_table,'student',$student);                          resource => $res,
             my $res_id     = &get_id($res_table,'resource',$res);                          machine  => $host,
 #            my $action_id  = &get_id($action_table,'action',$action);                          action   => $action,
             my $sql_time   = &Apache::lonmysql::sqltime($time);                          time => &Apache::lonmysql::sqltime($time));
             #  
             if (! defined($student_id) || $student_id eq '') {   
                 $warningflag.='student_id';   
             }  
             if (! defined($res_id) || $res_id eq '') {   
                 $warningflag.='res_id';   
             }  
 #            if (! defined($action_id) || $action_id eq '') {   
 #                $warningflag.='action_id';   
 #            }  
             if ($warningflag ne '') {  
                 print $error_fh 'full log entry:'.$log.$/;  
                 print $error_fh 'error on chunk:'.$chunk.$/;  
                 $logthis->('warningflag ('.$warningflag.') on chunk '.  
                            $/.$chunk.$/.'prevchunk = '.$/.$prevchunk);  
                 $prevchunk .= $chunk;  
                 next; # skip this chunk  
             }  
             #  
             my $store_values;  
             if ($action eq 'POST') {              if ($action eq 'POST') {
                 $store_values =                   $data{'action_values'} =
                     $dbh->quote(join('&',map { &escape($_); } @values));                      $dbh->quote(join('&',map { &escape($_); } @values));
             } else {              } else {
                 $store_values = $dbh->quote(join('&',@values));                  $data{'action_values'} = $dbh->quote(join('&',@values));
             }              }
             $time_this->('get_ids');              my $error = &store_entry($dbh,$tables,\%data);
             #              if ($error) {
             my $row = [$res_id,                  $logthis->('error store_entry:'.$error." on %data");
                        qq{'$sql_time'},  
                        $student_id,  
                        "'".$action."'",  
 #                       $action_id,  
                        qq{''},        # idx  
                        $machine_id,  
                        $store_values];  
             push(@RowData,$row);  
             $time_this->('push_row');  
             $prevchunk = $chunk;  
             #  
         }  
         $time_this->();  
         if ((scalar(@RowData) > 0) && ($linecount % 100 == 0)) {  
             my $result = &Apache::lonmysql::bulk_store_rows($activity_table,  
                                                             undef,  
                                                             \@RowData);  
             # $logthis->('result = '.$result);  
             $time_this->('bulk_store_rows');  
             if (! defined($result)) {  
                 my $error = &Apache::lonmysql::get_error();  
                 warn "Error occured during insert.".$error;  
                 $logthis->('error = '.$error);  
             }              }
             undef(@RowData);              $prevchunk = $chunk;
         }          }
     }      }
     if (@RowData) {      my $result = &store_entry($dbh,$tables);
         $time_this->();      if (! defined($result)) {
         $logthis->('storing '.$linecount);          my $error = &Apache::lonmysql::get_error();
         my $result = &Apache::lonmysql::bulk_store_rows($activity_table,          warn "Error occured during insert.".$error;
                                                         undef,          $logthis->('error = '.$error);
                                                         \@RowData);  
         $logthis->('result = '.$result);  
         $time_this->('bulk_store_rows');  
         if (! defined($result)) {  
             my $error = &Apache::lonmysql::get_error();  
             warn "Error occured during insert.".$error;  
             $logthis->('error = '.$error);  
         }  
         undef(@RowData);  
     }      }
     close IN;      close IN;
 #    print "Number of lines: ".$linecount.$/;  
 #    print "Number of inserts: ".$insertcount.$/;  
     return $linecount;      return $linecount;
 }  }
   
   
 ##  ##
 ## Somtimes, instead of doing something, doing nothing is appropriate.  ## default value for $logthis and $time_this
 sub nothing {  sub nothing {
     return;      return;
 }  }
   
 ##  ##
 ## Logging routine  ## Logging routine (look for $log)
 ##  ##
 sub log_to_file {  sub log_to_file {
     my ($input)=@_;      my ($input)=@_;
Line 643  sub outputtimes { Line 608  sub outputtimes {
   
 }  }
   
   sub latest_table_modification_time {
       my $latest_time;
       foreach my $table (@Activity_Table,@ID_Tables) {    
           my %tabledata = &Apache::lonmysql::table_information($table->{'id'});
           next if (! scalar(keys(%tabledata))); # table does not exist
           if (! defined($latest_time) ||
               $latest_time < $tabledata{'Update_time'}) {
               $latest_time = $tabledata{'Update_time'};
           }
       }
       return $latest_time;
   }
   
   sub check_for_missing_tables {
       my @wanted_tables = @_;
       # Check for missing tables
       my @Current_Tables = &Apache::lonmysql::tables_in_db();
       my %Found;
       foreach my $tablename (@Current_Tables) {
           foreach my $table (@wanted_tables) {
               if ($tablename eq  $table) {
                   $Found{$tablename}++;
               }
           }
       }
       $logthis->('Found tables '.join(',',keys(%Found)));
       my $missing_a_table = 0;
       foreach my $table (@wanted_tables) {
           if (! $Found{$table}) {
               $logthis->('Missing table '.$table);
               $missing_a_table = 1;
               last;
           }
       }
       return $missing_a_table;
   }
   
 ##  ##
 ## Use mysqldump to store backups of the tables  ## Use mysqldump to store backups of the tables
 ##  ##
 sub backup_tables {  sub backup_tables_as_sql {
     my ($gz_sql_filename) = @_;      my ($gz_sql_filename) = @_;
     my $command = qq{mysqldump --quote-names --opt loncapa };      my $command = qq{mysqldump --quote-names --opt loncapa };
     foreach my $table (@ID_Tables,@Activity_Table) {      foreach my $table (@ID_Tables,@Activity_Table) {
Line 663  sub backup_tables { Line 664  sub backup_tables {
 ##  ##
 ## Load in mysqldumped files  ## Load in mysqldumped files
 ##  ##
 sub load_backup_tables {  sub load_backup_sql_tables {
     my ($gz_sql_filename) = @_;      my ($gz_sql_filename) = @_;
     if (-s $gz_sql_filename) {      if (-s $gz_sql_filename) {
         $logthis->('loading data from gzipped sql file');          $logthis->('loading data from gzipped sql file');
Line 732  sub read_id_tables { Line 733  sub read_id_tables {
   
 sub get_id {  sub get_id {
     my ($table,$fieldname,$value) = @_;      my ($table,$fieldname,$value) = @_;
     if (exists($IDs{$table}->{$value})) {      if (exists($IDs{$table}->{$value}) && $IDs{$table}->{$value} =~ /^\d+$/) {
         return $IDs{$table}->{$value};          return $IDs{$table}->{$value};
     } else {      } else {
         # insert into the table - if the item already exists, that is          # insert into the table - if the item already exists, that is
         # okay.          # okay.
         my $result = &Apache::lonmysql::store_row($table,[undef,$value]);          my $result = &Apache::lonmysql::store_row($table,[undef,$value]);
         if (! defined($result)) {          if (! defined($result)) {
             warn("Got error on id insert for $value\n".&Apache::lonmysql::get_error());              warn("Got error on id insert for $value\n".
                    &Apache::lonmysql::get_error());
         }          }
         # get the id          # get the id
         my @Data =           my $id = &Apache::lonmysql::get_dbh()->{'mysql_insertid'};
             &Apache::lonmysql::get_rows($table,qq{$fieldname='$value'});          if (defined($id)) {
         if (@Data) {              $IDs{$table}->{$value}=$id;
             $IDs{$table}->{$value}=$Data[0]->[0];  
             return $IDs{$table}->{$value};  
         } else {          } else {
             $logthis->("Unable to retrieve id for $table $fieldname $value");              $logthis->("Unable to retrieve id for $table $fieldname $value");
             return undef;              return undef;
Line 756  sub get_id { Line 756  sub get_id {
   
 } # End of ID scoping  } # End of ID scoping
   
   ###############################################################
   ###############################################################
   ##
   ##   Save as XML
   ##
   ###############################################################
   ###############################################################
   sub backup_tables_as_xml {
       my ($filename,$tables) = @_;
       open(XMLFILE,"|gzip - > $filename") || return ('error:unable to write '.$filename);
       my $query = qq{
           SELECT B.resource,
                  A.time,
                  A.idx,
                  C.student,
                  A.action,
                  E.machine,
                  A.action_values 
               FROM $tables->{'activity'} AS A
               LEFT JOIN $tables->{'res'}      AS B ON B.res_id=A.res_id 
               LEFT JOIN $tables->{'student'}  AS C ON C.student_id=A.student_id 
               LEFT JOIN $tables->{'machine'}  AS E ON E.machine_id=A.machine_id
               ORDER BY A.time DESC
           };
       $query =~ s/\s+/ /g;
       my $dbh = &Apache::lonmysql::get_dbh();
       my $sth = $dbh->prepare($query);
       if (! $sth->execute()) {
           $logthis->('<font color="blue">'.
                      'WARNING: Could not retrieve from database:'.
                      $sth->errstr().'</font>');
           return undef;
       } else {
           my ($res,$sqltime,$idx,$student,$action,$machine,$action_values);
           if ($sth->bind_columns(\$res,\$sqltime,\$idx,\$student,\$action,
                                  \$machine,\$action_values)) {
               
               while ($sth->fetch) {
                   print XMLFILE '<row>'.
                       qq{<resource>$res</resource>}.
                       qq{<time>$sqltime</time>}.
                       qq{<idx>$idx</idx>}.
                       qq{<student>$student</student>}.
                       qq{<action>$action</action>}.
                       qq{<machine>$machine</machine>}.
                       qq{<action_values>$action_values</action_values>}.
                       '</row>'.$/;
               }
           } else {
               warn "Unable to bind to columns.\n";
               return undef;
           }
       }
       close XMLFILE;
       return;
   }
   
   ###############################################################
   ###############################################################
   ##
   ##   load as xml
   ##
   ###############################################################
   ###############################################################
   {
       my @fields = ('resource','time',
                     'student','action','idx','machine','action_values');
       my %ids = ();
   sub load_backup_xml_tables {
       my ($filename,$tables) = @_;
       my $dbh = &Apache::lonmysql::get_dbh();
       my $xmlfh;
       open($xmlfh,"cat $filename | gzip -d - |");
       if (! defined($xmlfh)) {
           return ('error:unable to read '.$filename);
       }
       #
       %ids = (resource=> {"\0count"=>1},
               student=> {"\0count"=>1},
               machine=> {"\0count"=>1});
       #
       my %data;
       while (my $inputline = <$xmlfh>) {
           my ($resource,$time,undef,$student,$action,$machine,$action_values) = 
               ($inputline =~ m{<row>
                                    <resource>(.*)</resource>
                                    <time>(.*)</time>
                                    <idx>(.*)</idx>
                                    <student>(.*)</student>
                                    <action>(.*)</action>
                                    <machine>(.*)</machine>
                                    <action_values>(.*)</action_values>
                                    </row>$
                                }x
                );
           my $resource_id = &xml_get_id('resource',$resource);
           my $student_id  = &xml_get_id('student',$student);
           my $machine_id  = &xml_get_id('machine',$machine);
           &xml_store_activity_row(map { defined($_)?$dbh->quote($_):'' 
                                     } ($resource_id,
                                        $time,
                                        $student_id,
                                        $action,
                                        'NULL',
                                        $machine_id,
                                        $action_values));
       }
       &xml_store_activity_row();
       close($xmlfh);
       # Store id tables
       while (my ($id_name,$id_data) = each(%ids)) {
           if ($id_name eq 'resource') { $id_name = 'res'; }
           delete($id_data->{"\0count"});
           &xml_store_id_table($id_name,$id_data);
       }
       return;
   }
   
   sub xml_get_id {
       my ($table,$element) = @_;
       if (! exists($ids{$table}->{$element})) {
           $ids{$table}->{$element} = $ids{$table}->{"\0count"}++;
       }
       return $ids{$table}->{$element};
   }
   
   {
       my @data_rows;
   sub xml_store_activity_row {
       my @data = @_;
       if (scalar(@data)) {
           push(@data_rows,[@data]);
       }
       if (! scalar(@data) || scalar(@data_rows) > 500) {
           if (! &Apache::lonmysql::bulk_store_rows($tables{'activity'},
                                                    scalar(@{$data_rows[0]}),
                                                    \@data_rows)) {
               $logthis->("Error:".&Apache::lonmysql::get_error());
               warn("Error:".&Apache::lonmysql::get_error());
           } else {
               undef(@data_rows);
           }
       }
       return;
   }
   
   }
   
   sub xml_store_id_table {
       my ($table,$tabledata) =@_;
       my $dbh = &Apache::lonmysql::get_dbh();
       if (! &Apache::lonmysql::bulk_store_rows
           ($tables{$table},2,
            [map{[$tabledata->{$_},$dbh->quote($_)]} keys(%$tabledata)])) {
           $logthis->("Error:".&Apache::lonmysql::get_error());
           warn "Error:".&Apache::lonmysql::get_error().$/;
       }
   }
   
   } # End of load xml scoping
   
   #######################################################################
   #######################################################################
   ##
   ## store_entry - accumulate data to be inserted into the database
   ##
   ## Pass no values in to clear accumulator
   ## Pass ($dbh,\%tables) to initiate storage of values
   ## Pass ($dbh,\%tables,\%data) to use normally
   ##
   #######################################################################
   #######################################################################
   {
       my @rows;
       my $max_row_count = 100;
   
   sub store_entry {
       if (! @_) {
           undef(@rows);
           return '';
       }
       my ($dbh,$tables,$data) = @_;
       return if (! defined($tables));
       if (defined($data)) {
           my $error;
           foreach my $field ('student','resource','action','time') {
               if (! defined($data->{$field}) || $data->{$field} eq ':' ||
                   $data->{$field}=~ /^\s*$/) {
                   $error.=$field.',';
               }
           }
           if ($error) { $error=~s/,$//; return $error; }
           #
           my $student_id = &get_id($tables->{'student'},'student',
                                    $data->{'student'});
           my $res_id     = &get_id($tables->{'res'},
                                    'resource',$data->{'resource'});
           my $machine_id = &get_id($tables->{'machine'},
                                    'machine',$data->{'machine'});
           my $idx = $data->{'idx'}; if (! $idx) { $idx = "''"; }
           #
           push(@rows,[$res_id,
                       qq{'$data->{'time'}'},
                       $student_id,
                       qq{'$data->{'action'}'},
                       $idx,
                       $machine_id,
                       $data->{'action_values'}]);
       }
       if (defined($tables) &&
           ( (! defined($data) && scalar(@rows)) || scalar(@rows)>$max_row_count)
           ){
           # Store the rows
           my $result =
               &Apache::lonmysql::bulk_store_rows($tables->{'activity'},
                                                  undef,
                                                  \@rows);
           if (! defined($result)) {
               my $error = &Apache::lonmysql::get_error();
               warn "Error occured during insert.".$error;
               return $error;
           }
           undef(@rows);
           return $result if (! defined($data));
       }
       return '';
   }
   
   } # end of scope for &store_entry
   
 ###############################################################  ###############################################################
 ###############################################################  ###############################################################

Removed from v.1.13  
changed lines
  Added in v.1.19


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>