Diff for /loncom/metadata_database/searchcat.pl between versions 1.24 and 1.82

version 1.24, 2002/10/18 13:54:31 version 1.82, 2014/01/16 13:31:31
Line 26 Line 26
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # YEAR=2001  
 # 04/14/2001, 04/16/2001 Scott Harrison  
 #  
 # YEAR=2002  
 # 05/11/2002 Scott Harrison  
 #  
 ###  ###
   
 # This script goes through a LON-CAPA resource  =pod
 # directory and gathers metadata.  
 # The metadata is entered into a SQL database.  
   
 use lib '/home/httpd/lib/perl/';  =head1 NAME
 use LONCAPA::Configuration;  
   B<searchcat.pl> - put authoritative filesystem data into sql database.
   
   =head1 SYNOPSIS
   
   Ordinarily this script is to be called from a loncapa cron job
   (CVS source location: F<loncapa/loncom/cron/loncapa>; typical
   filesystem installation location: F</etc/cron.d/loncapa>).
   
   Here is the cron job entry.
   
   C<# Repopulate and refresh the metadata database used for the search catalog.>
   C<10 1 * * 7    www    /home/httpd/perl/searchcat.pl>
   
   This script only allows itself to be run as the user C<www>.
   
   =head1 DESCRIPTION
   
   This script goes through a loncapa resource directory and gathers metadata.
   The metadata is entered into a SQL database.
   
   This script also does general database maintenance such as reformatting
   the C<loncapa:metadata> table if it is deprecated.
   
   This script evaluates dynamic metadata from the authors'
   F<nohist_resevaldata.db> database file in order to store it in MySQL.
   
   This script is playing an increasingly important role for a loncapa
   library server.  The proper operation of this script is critical for a smooth
   and correct user experience.
   
   =cut
   
   use strict;
   use DBI;
   use lib '/home/httpd/lib/perl/';
   use LONCAPA::lonmetadata;
   use LONCAPA;
   use Getopt::Long;
 use IO::File;  use IO::File;
 use HTML::TokeParser;  use HTML::TokeParser;
 use DBI;  
 use GDBM_File;  use GDBM_File;
 use POSIX qw(strftime mktime);  use POSIX qw(strftime mktime);
   use Mail::Send;
   use Apache::loncommon();
   
 my @metalist;  use Apache::lonnet();
   
   use File::Find;
   
 # ----------------------------------------------------- Un-Escape Special Chars  #
   # Set up configuration options
   my ($simulate,$oneuser,$help,$verbose,$logfile,$debug);
   GetOptions (
               'help'     => \$help,
               'simulate' => \$simulate,
               'only=s'   => \$oneuser,
               'verbose=s'  => \$verbose,
               'debug' => \$debug,
               );
   
 sub unescape {  if ($help) {
     my $str=shift;      print <<"ENDHELP";
     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;  $0
     return $str;  Rebuild and update the LON-CAPA metadata database. 
   Options:
       -help          Print this help
       -simulate      Do not modify the database.
       -only=user     Only compute for the given user.  Implies -simulate   
       -verbose=val   Sets logging level, val must be a number
       -debug         Turns on debugging output
   ENDHELP
       exit 0;
 }  }
   
 # -------------------------------------------------------- Escape Special Chars  if (! defined($debug)) {
       $debug = 0;
   }
   
 sub escape {  if (! defined($verbose)) {
     my $str=shift;      $verbose = 0;
     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;  
     return $str;  
 }  }
   
   if (defined($oneuser)) {
       $simulate=1;
   }
   
 # ------------------------------------------- Code to evaluate dynamic metadata  ##
   ## Use variables for table names so we can test this routine a little easier
   my %oldnames = (
                    'metadata'    => 'metadata',
                    'portfolio'   => 'portfolio_metadata',
                    'access'      => 'portfolio_access',
                    'addedfields' => 'portfolio_addedfields',
                    'allusers'    => 'allusers',
                  );
   
   my %newnames;
   # new table names -  append pid to have unique temporary tables
   foreach my $key (keys(%oldnames)) {
       $newnames{$key} = 'new'.$oldnames{$key}.$$;
   }
   
 sub dynamicmeta {  
 #  #
   # Only run if machine is a library server
   exit if ($Apache::lonnet::perlvar{'lonRole'} ne 'library');
   my $hostid = $Apache::lonnet::perlvar{'lonHostID'};
   
 #  #
 # Do nothing for now ...  #  Make sure this process is running from user=www
   my $wwwid=getpwnam('www');
   if ($wwwid!=$<) {
       my $emailto="$Apache::lonnet::perlvar{'lonAdmEMail'},$Apache::lonnet::perlvar{'lonSysEMail'}";
       my $subj="LON: $Apache::lonnet::perlvar{'lonHostID'} User ID mismatch";
       system("echo 'User ID mismatch. searchcat.pl must be run as user www.' |\
    mail -s '$subj' $emailto > /dev/null");
       exit 1;
   }
 #  #
   # Let people know we are running
   open(LOG,'>>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/logs/searchcat.log');
   &log(0,'==== Searchcat Run '.localtime()."====");
   
   
   if ($debug) {
       &log(0,'simulating') if ($simulate);
       &log(0,'only processing user '.$oneuser) if ($oneuser);
       &log(0,'verbosity level = '.$verbose);
   }
 #  #
     return;  # Connect to database
   my $dbh;
   if (! ($dbh = DBI->connect("DBI:mysql:loncapa","www",$Apache::lonnet::perlvar{'lonSqlAccess'},
                             { RaiseError =>0,PrintError=>0}))) {
       &log(0,"Cannot connect to database!");
       die "MySQL Error: Cannot connect to database!\n";
   }
   # This can return an error and still be okay, so we do not bother checking.
   # (perhaps it should be more robust and check for specific errors)
   foreach my $key (keys(%newnames)) {
       if ($newnames{$key} ne '') {
           $dbh->do('DROP TABLE IF EXISTS '.$newnames{$key});
       }
   }
   
 #  #
 # ..., but stuff below already works  # Create the new metadata, portfolio and allusers tables
   foreach my $key (keys(%newnames)) {
       if ($newnames{$key} ne '') { 
           my $request =
                &LONCAPA::lonmetadata::create_metadata_storage($newnames{$key},$oldnames{$key});
           $dbh->do($request);
           if ($dbh->err) {
               $dbh->disconnect();
               &log(0,"MySQL Error Create: ".$dbh->errstr);
               die $dbh->errstr;
           }
       }
   }
   
 #  #
     my $url=&declutter(shift);  # find out which users we need to examine
     $url=~s/\.meta$//;  my @domains = sort(&Apache::lonnet::current_machine_domains());
     my %returnhash=();  &log(9,'domains ="'.join('","',@domains).'"');
     my ($adomain,$aauthor)=($url=~/^(\w+)\/(\w+)\//);  
     my $prodir=&propath($adomain,$aauthor);  foreach my $dom (@domains) {
     if (tie(%evaldata,'GDBM_File',      &log(9,'domain = '.$dom);
             $prodir.'/nohist_resevaldata.db',&GDBM_WRCREAT(),0640)) {      opendir(RESOURCES,"$Apache::lonnet::perlvar{'lonDocRoot'}/res/$dom");
        my %sum=();      my @homeusers = 
        my %cnt=();          grep {
        my %listitems=('count'        => 'add',              &ishome("$Apache::lonnet::perlvar{'lonDocRoot'}/res/$dom/$_");
                       'course'       => 'add',          } grep { 
                       'avetries'     => 'avg',              !/^\.\.?$/;
                       'stdno'        => 'add',          } readdir(RESOURCES);
                       'difficulty'   => 'avg',      closedir RESOURCES;
                       'clear'        => 'avg',      &log(5,'users = '.$dom.':'.join(',',@homeusers));
                       'technical'    => 'avg',      #
                       'helpful'      => 'avg',      if ($oneuser) {
                       'correct'      => 'avg',          @homeusers=($oneuser);
                       'depth'        => 'avg',      }
                       'comments'     => 'app',  
                       'usage'        => 'cnt'      #
                       );      # Loop through the users
        my $regexp=$url;      foreach my $user (@homeusers) {
        $regexp=~s/(\W)/\\$1/g;          &log(0,"=== User: ".$user);
        $regexp='___'.$regexp.'___([a-z]+)$';          &process_dynamic_metadata($user,$dom);
        foreach (keys %evaldata) {          #
  my $key=&unescape($_);          # Use File::Find to get the files we need to read/modify
  if ($key=~/$regexp/) {          find(
     my $ctype=$1;               {preprocess => \&only_meta_files,
             if (defined($cnt{$ctype})) {                 #wanted     => \&print_filename,
                $cnt{$ctype}++;                 #wanted     => \&log_metadata,
             } else {                 wanted     => \&process_meta_file,
                $cnt{$ctype}=1;                 no_chdir   => 1,
             }               }, join('/',($Apache::lonnet::perlvar{'lonDocRoot'},'res',$dom,$user)) );
             unless ($listitems{$ctype} eq 'app') {      }
                if (defined($sum{$ctype})) {      # Search for all users and public portfolio files
                   $sum{$ctype}+=$evaldata{$_};      my (%allusers,%portusers,%courses);
           } else {      if ($oneuser) {
                   $sum{$ctype}=$evaldata{$_};          %portusers = (
        }                          $oneuser => '',
                          );
           %allusers = (
                           $oneuser => '',
                          );
           %courses = &courseiddump($dom,'.',1,'.','.',$oneuser,undef,
                                    undef,'.');
       } else {
           # get courseIDs for domain on current machine
           %courses=&Apache::lonnet::courseiddump($dom,'.',1,'.','.','.',1,[$hostid],'.');
           my $dir = $Apache::lonnet::perlvar{lonUsersDir}.'/'.$dom;
           &descend_tree($dom,$dir,0,\%portusers,\%allusers);
       }
       foreach my $uname (keys(%portusers)) {
           my $urlstart = '/uploaded/'.$dom.'/'.$uname;
           my $pathstart = &propath($dom,$uname).'/userfiles';
           my $is_course = '';
           if (exists($courses{$dom.'_'.$uname})) {
               $is_course = 1;
           }
           my $curr_perm = &Apache::lonnet::get_portfile_permissions($dom,$uname);
           my %access = &Apache::lonnet::get_access_controls($curr_perm);
           foreach my $file (keys(%access)) {
               my ($group,$url,$fullpath);
               if ($is_course) {
                   ($group, my ($path)) = ($file =~ /^(\w+)(\/.+)$/);
                   $fullpath = $pathstart.'/groups/'.$group.'/portfolio'.$path;
                   $url = $urlstart.'/groups/'.$group.'/portfolio'.$path;
             } else {              } else {
                if (defined($sum{$ctype})) {                  $fullpath = $pathstart.'/portfolio'.$file;
                   if ($evaldata{$_}) {                  $url = $urlstart.'/portfolio'.$file;
                      $sum{$ctype}.='<hr>'.$evaldata{$_};              }
           }              if (ref($access{$file}) eq 'HASH') {
         } else {                  my %portaccesslog = 
              $sum{$ctype}=''.$evaldata{$_};                      &LONCAPA::lonmetadata::process_portfolio_access_data($dbh,
        }                             $simulate,\%newnames,$url,$fullpath,$access{$file});
     }                  &portfolio_logging(%portaccesslog);
     if ($ctype eq 'count') {              }
        delete($evaldata{$_});              my %portmetalog = &LONCAPA::lonmetadata::process_portfolio_metadata($dbh,$simulate,\%newnames,$url,$fullpath,$is_course,$dom,$uname,$group);
             }              &portfolio_logging(%portmetalog);
  }          }
       }      }
       foreach (keys %cnt) {      my (%names_by_id,,%ids_by_name,%idstodelete,%idstoadd,%duplicates);
          if ($listitems{$_} eq 'avg') {      unless ($simulate || $oneuser) {
      $returnhash{$_}=int(($sum{$_}/$cnt{$_})*100.0+0.5)/100.0;          my $idshashref;
          } elsif ($listitems{$_} eq 'cnt') {          $idshashref = &tie_domain_hash($dom, "ids", &GDBM_WRCREAT());
              $returnhash{$_}=$cnt{$_};          if (ref($idshashref) eq 'HASH') {
          } else {              %names_by_id = %{$idshashref};
              $returnhash{$_}=$sum{$_};              while (my ($id,$uname) = each(%{$idshashref}) ) {
          }                  $id = &unescape($id);
      }                  $uname = &unescape($uname); 
      if ($returnhash{'count'}) {                  $names_by_id{$id} = $uname;
          my $newkey=$$.'_'.time.'_searchcat___'.&escape($url).'___count';                  push(@{$ids_by_name{$uname}},$id);
          $evaldata{$newkey}=$returnhash{'count'};              }
      }              &untie_domain_hash($idshashref);
      untie(%evaldata);          }
    }      }
    return %returnhash;      # Update allusers
 }      foreach my $uname (keys(%allusers)) {
             next if (exists($courses{$dom.'_'.$uname}));
 # ----------------- Code to enable 'find' subroutine listing of the .meta files          my %userdata = 
 require "find.pl";              &Apache::lonnet::get('environment',['firstname','lastname',
 sub wanted {                  'middlename','generation','id','permanentemail'],$dom,$uname);
     (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&          unless ($simulate || $oneuser) {
     -f _ &&              my $addid;
     /^.*\.meta$/ && !/^.+\.\d+\.[^\.]+\.meta$/ &&              if ($userdata{'id'} ne '') {
     push(@metalist,"$dir/$_");                  $addid = $userdata{'id'};
 }                  $addid=~tr/A-Z/a-z/;
               }
 # ---------------  Read loncapa_apache.conf and loncapa.conf and get variables              if (exists($ids_by_name{$uname})) {
 my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');                  if (ref($ids_by_name{$uname}) eq 'ARRAY') {
 my %perlvar=%{$perlvarref};                      if (scalar(@{$ids_by_name{$uname}}) > 1) {
 undef $perlvarref; # remove since sensitive and not needed                          &log(0,"Multiple employee/student IDs found in ids.db for $uname:$dom -- ".join(', ',@{$ids_by_name{$uname}}));
 delete $perlvar{'lonReceipt'}; # remove since sensitive and not needed                      }
                       foreach my $id (@{$ids_by_name{$uname}}) {
                           if ($id eq $userdata{'id'}) {
                               undef($addid);
                           } else { 
                               $idstodelete{$id} = $uname;
                           }
                       }
                   }
               }
               if ($addid ne '') {
                   if (exists($idstoadd{$addid})) {
                       push(@{$duplicates{$addid}},$uname);
                   } else {
                       $idstoadd{$addid} = $uname;
                   }
               }
           }
           
           $userdata{'username'} = $uname;
           $userdata{'domain'} = $dom;
           my %alluserslog = 
               &LONCAPA::lonmetadata::process_allusers_data($dbh,$simulate,
                   \%newnames,$uname,$dom,\%userdata);
           foreach my $item (keys(%alluserslog)) {
               &log(0,$alluserslog{$item});
           }
       }
       unless ($simulate || $oneuser) {
           if (keys(%idstodelete) > 0) {
               my %resulthash = &Apache::lonnet::iddel($dom,\%idstodelete,$hostid);
               if ($resulthash{$hostid} eq 'ok') {
                   foreach my $id (sort(keys(%idstodelete))) {
                       &log(0,"Record deleted from ids.db for $dom -- $id => ".$idstodelete{$id});
                   }
               } else {
                   &log(0,"Error: '$resulthash{$hostid}' occurred when attempting to delete records from ids.db for $dom");
               }
           }
           if (keys(%idstoadd) > 0) {
               my $idmessage = '';
               my %newids;
               foreach my $addid (sort(keys(%idstoadd))) {
                   if ((exists($names_by_id{$addid})) && ($names_by_id{$addid} ne $idstoadd{$addid})  && !($idstodelete{$addid})) {
                       &log(0,"Two usernames associated with a single ID $addid in domain: $dom: $names_by_id{$addid} (current) and $idstoadd{$addid}\n");
                       $idmessage .= "$addid,$names_by_id{$addid},$idstoadd{$addid}\n";
                   } else {
                       $newids{$addid} = $idstoadd{$addid};
                   }
               }
               if (keys(%newids) > 0) {
                   my $putresult = &Apache::lonnet::put_dom('ids',\%idstoadd,$dom,$hostid);
                   if ($putresult eq 'ok') {
                       foreach my $id (sort(keys(%idstoadd))) {
                           &log(0,"Record added to ids.db for $dom -- $id => ".$idstoadd{$id});
                       }
                   } else {
                       &log(0,"Error: '$putresult' occurred when attempting to add records to ids.db for $dom"); 
                   }
               }
               if ($idmessage) {
                   my $to = &Apache::loncommon::build_recipient_list(undef,'idconflictsmail',$dom);
                   if ($to ne '') {
                       my $msg = new Mail::Send;
                       $msg->to($to);
                       $msg->subject('LON-CAPA studentIDs conflict');
                       my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
                       my $hostname = &Apache::lonnet::hostname($lonhost);
                       my $replytoaddress = 'do-not-reply@'.$hostname;
                       $msg->add('Reply-to',$replytoaddress);
                       $msg->add('From','www@'.$hostname);
                       $msg->add('Content-type','text/plain; charset=UTF-8');
                       if (my $fh = $msg->open()) {
                           print $fh 
                               'The following IDs are used for more than one user in your domain:'."\n".
                               'Each row contains: Student/Employee ID, Current username in ids.db file, '.
                               'Additional username'."\n\n".
                               $idmessage;
                           $fh->close;
                       }
                   }
               }
           }
           if (keys(%duplicates) > 0) {
               foreach my $id (sort(keys(%duplicates))) {
                   &log(0,"Duplicate IDs found for entries to add to ids.db in $dom -- $id => $idstodelete{$id}");
               }
           }
       }
   }
   
 # ------------------------------------- Only run if machine is a library server  #
 exit unless $perlvar{'lonRole'} eq 'library';  # Rename the tables
   if (! $simulate) {
       foreach my $key (keys(%oldnames)) {
           if (($oldnames{$key} ne '') && ($newnames{$key} ne '')) {
               $dbh->do('DROP TABLE IF EXISTS '.$oldnames{$key});
               if (! $dbh->do('RENAME TABLE '.$newnames{$key}.' TO '.$oldnames{$key})) {
                   &log(0,"MySQL Error Rename: ".$dbh->errstr);
                   die $dbh->errstr;
               } else {
                   &log(1,"MySQL table rename successful for $key.");
               }
           }
       }
   }
   if (! $dbh->disconnect) {
       &log(0,"MySQL Error Disconnect: ".$dbh->errstr);
       die $dbh->errstr;
   }
   ##
   ## Finished!
   &log(0,"==== Searchcat completed ".localtime()." ====");
   close(LOG);
   
   &write_type_count();
   &write_copyright_count();
   
   exit 0;
   
   ##
   ## Status logging routine.  Inputs: $level, $message
   ## 
   ## $level 0 should be used for normal output and error messages
   ##
   ## $message does not need to end with \n.  In the case of errors
   ## the message should contain as much information as possible to
   ## help in diagnosing the problem.
   ##
   sub log {
       my ($level,$message)=@_;
       $level = 0 if (! defined($level));
       if ($verbose >= $level) {
           print LOG $message.$/;
       }
   }
   
 my $dbh;  sub portfolio_logging {
 # ------------------------------------- Make sure that database can be accessed      my (%portlog) = @_;
 {      foreach my $key (keys(%portlog)) {
     unless (          if (ref($portlog{$key}) eq 'HASH') {
     $dbh = DBI->connect("DBI:mysql:loncapa","www",$perlvar{'lonSqlAccess'},{ RaiseError =>0,PrintError=>0})              foreach my $item (keys(%{$portlog{$key}})) {
     ) {                   &log(0,$portlog{$key}{$item});
  print "Cannot connect to database!\n";              }
  exit;          }
     }      }
     my $make_metadata_table = "CREATE TABLE IF NOT EXISTS metadata (".  }
         "title TEXT, author TEXT, subject TEXT, url TEXT, keywords TEXT, ".  
         "version TEXT, notes TEXT, abstract TEXT, mime TEXT, language TEXT, ".  
         "creationdate DATETIME, lastrevisiondate DATETIME, owner TEXT, ".  
         "copyright TEXT, FULLTEXT idx_title (title), ".  
         "FULLTEXT idx_author (author), FULLTEXT idx_subject (subject), ".  
         "FULLTEXT idx_url (url), FULLTEXT idx_keywords (keywords), ".  
         "FULLTEXT idx_version (version), FULLTEXT idx_notes (notes), ".  
         "FULLTEXT idx_abstract (abstract), FULLTEXT idx_mime (mime), ".  
         "FULLTEXT idx_language (language), FULLTEXT idx_owner (owner), ".  
         "FULLTEXT idx_copyright (copyright)) TYPE=MYISAM";  
     # It would sure be nice to have some logging mechanism.  
     $dbh->do($make_metadata_table);  
 }  
   
 # ------------------------------------------------------------- get .meta files  
 opendir(RESOURCES,"$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}");  
 my @homeusers=grep  
           {&ishome("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$_")}  
           grep {!/^\.\.?$/} readdir(RESOURCES);  
 closedir RESOURCES;  
 foreach my $user (@homeusers) {  
     &find("$perlvar{'lonDocRoot'}/res/$perlvar{'lonDefDomain'}/$user");  
 }  
   
 # -- process each file to get metadata and put into search catalog SQL database  
 # Also, check to see if already there.  
 # I could just delete (without searching first), but this works for now.  
 foreach my $m (@metalist) {  
     my $ref=&metadata($m);  
     my $m2='/res/'.&declutter($m);  
     $m2=~s/\.meta$//;  
     &dynamicmeta($m2);  
     my $q2="select * from metadata where url like binary '$m2'";  
     my $sth = $dbh->prepare($q2);  
     $sth->execute();  
     my $r1=$sth->fetchall_arrayref;  
     if (@$r1) {  
  $sth=$dbh->prepare("delete from metadata where url like binary '$m2'");  
         $sth->execute();  
     }  
     $sth=$dbh->prepare('insert into metadata values ('.  
   '"'.delete($ref->{'title'}).'"'.','.  
   '"'.delete($ref->{'author'}).'"'.','.  
   '"'.delete($ref->{'subject'}).'"'.','.  
   '"'.$m2.'"'.','.  
   '"'.delete($ref->{'keywords'}).'"'.','.  
   '"'.'current'.'"'.','.  
   '"'.delete($ref->{'notes'}).'"'.','.  
   '"'.delete($ref->{'abstract'}).'"'.','.  
   '"'.delete($ref->{'mime'}).'"'.','.  
   '"'.delete($ref->{'language'}).'"'.','.  
   '"'.sqltime(delete($ref->{'creationdate'})).'"'.','.  
   '"'.sqltime(delete($ref->{'lastrevisiondate'})).'"'.','.  
   '"'.delete($ref->{'owner'}).'"'.','.  
   '"'.delete($ref->{'copyright'}).'"'.')');  
     $sth->execute();  
 }  
   
 # ----------------------------------------------------------- Clean up database  
 # Need to, perhaps, remove stale SQL database records.  
 # ... not yet implemented  
   
 # --------------------------------------------------- Close database connection  sub descend_tree {
 $dbh->disconnect;      my ($dom,$dir,$depth,$allportusers,$alldomusers) = @_;
       if (-d $dir) {
           opendir(DIR,$dir);
           my @contents = grep(!/^\./,readdir(DIR));
           closedir(DIR);
           $depth ++;
           foreach my $item (@contents) {
               if ($depth < 4) {
                   &descend_tree($dom,$dir.'/'.$item,$depth,$allportusers,$alldomusers);
               } else {
                   if (-e $dir.'/'.$item.'/file_permissions.db') {
                       $$allportusers{$item} = '';
                   }
                   if (-e $dir.'/'.$item.'/passwd') {
                       $$alldomusers{$item} = '';
                   }
               }       
           }
       } 
   }
   
   ########################################################
   ########################################################
   ###                                                  ###
   ###          File::Find support routines             ###
   ###                                                  ###
   ########################################################
   ########################################################
   ##
   ## &only_meta_files
   ##
   ## Called by File::Find.
   ## Takes a list of files/directories in and returns a list of files/directories
   ## to search.
   sub only_meta_files {
       my @PossibleFiles = @_;
       my @ChosenFiles;
       foreach my $file (@PossibleFiles) {
           if ( ($file =~ /\.meta$/ &&            # Ends in meta
                 $file !~ /\.\d+\.[^\.]+\.meta$/  # is not for a prior version
                ) || (-d $File::Find::dir."/".$file )) { # directories are okay
                    # but we do not want /. or /..
               push(@ChosenFiles,$file);
           }
       }
       return @ChosenFiles;
   }
   
   ##
   ##
   ## Debugging routines, use these for 'wanted' in the File::Find call
   ##
   sub print_filename {
       my ($file) = $_;
       my $fullfilename = $File::Find::name;
       if ($debug) {
           if (-d $file) {
               &log(5," Got directory ".$fullfilename);
           } else {
               &log(5," Got file ".$fullfilename);
           }
       }
       $_=$file;
   }
   
   sub log_metadata {
       my ($file) = $_;
       my $fullfilename = $File::Find::name;
       return if (-d $fullfilename); # No need to do anything here for directories
       if ($debug) {
           &log(6,$fullfilename);
           my $ref = &metadata($fullfilename);
           if (! defined($ref)) {
               &log(6,"    No data");
               return;
           }
           while (my($key,$value) = each(%$ref)) {
               &log(6,"    ".$key." => ".$value);
           }
           &count_copyright($ref->{'copyright'});
       }
       $_=$file;
   }
   
 # ---------------------------------------------------------------- Get metadata  ##
 # significantly altered from subroutine present in lonnet  ## process_meta_file
   ##   Called by File::Find. 
   ##   Only input is the filename in $_.  
   sub process_meta_file {
       my ($file) = $_;
       my $filename = $File::Find::name; # full filename
       return if (-d $filename); # No need to do anything here for directories
       #
       &log(3,$filename) if ($debug);
       #
       my $ref = &metadata($filename);
       #
       # $url is the original file url, not the metadata file
       my $target = $filename;
       $target =~ s/\.meta$//;
       my $url='/res/'.&declutter($target);
       &log(3,"    ".$url) if ($debug);
       #
       # Ignore some files based on their metadata
       if ($ref->{'obsolete'}) { 
           &log(3,"obsolete") if ($debug);
           return; 
       }
       &count_copyright($ref->{'copyright'});
       if ($ref->{'copyright'} eq 'private') { 
           &log(3,"private") if ($debug);
           return; 
       }
       #
       # Find the dynamic metadata
       my %dyn;
       if ($url=~ m:/default$:) {
           $url=~ s:/default$:/:;
           &log(3,"Skipping dynamic data") if ($debug);
       } else {
           &log(3,"Retrieving dynamic data") if ($debug);
           %dyn=&get_dynamic_metadata($url);
           &count_type($url);
       }
       &LONCAPA::lonmetadata::getfiledates($ref,$target);
       #
       my %Data = (
                   %$ref,
                   %dyn,
                   'url'=>$url,
                   'version'=>'current');
       if (! $simulate) {
           my ($count,$err) = 
             &LONCAPA::lonmetadata::store_metadata($dbh,$newnames{'metadata'},
                                                   'metadata',\%Data);
           if ($err) {
               &log(0,"MySQL Error Insert: ".$err);
           }
           if ($count < 1) {
               &log(0,"Unable to insert record into MySQL database for $url");
           }
       }
       #
       # Reset $_ before leaving
       $_ = $file;
   }
   
   ########################################################
   ########################################################
   ###                                                  ###
   ###  &metadata($uri)                                 ###
   ###   Retrieve metadata for the given file           ###
   ###                                                  ###
   ########################################################
   ########################################################
 sub metadata {  sub metadata {
     my ($uri,$what)=@_;      my ($uri) = @_;
     my %metacache;      my %metacache=();
     $uri=&declutter($uri);      $uri=&declutter($uri);
     my $filename=$uri;      my $filename=$uri;
     $uri=~s/\.meta$//;      $uri=~s/\.meta$//;
     $uri='';      $uri='';
     unless ($metacache{$uri.'keys'}) {      if ($filename !~ /\.meta$/) { 
         unless ($filename=~/\.meta$/) { $filename.='.meta'; }          $filename.='.meta';
  my $metastring=&getfile($perlvar{'lonDocRoot'}.'/res/'.$filename);      }
         my $parser=HTML::TokeParser->new(\$metastring);      my $metastring = 
         my $token;          &LONCAPA::lonmetadata::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$filename);
         while ($token=$parser->get_token) {      return undef if (! defined($metastring));
            if ($token->[0] eq 'S') {      my $parser=HTML::TokeParser->new(\$metastring);
       my $entry=$token->[1];      my $token;
               my $unikey=$entry;      while ($token=$parser->get_token) {
               if (defined($token->[2]->{'part'})) {           if ($token->[0] eq 'S') {
                  $unikey.='_'.$token->[2]->{'part'};               my $entry=$token->[1];
       }              my $unikey=$entry;
               if (defined($token->[2]->{'name'})) {               if (defined($token->[2]->{'part'})) { 
                  $unikey.='_'.$token->[2]->{'name'};                   $unikey.='_'.$token->[2]->{'part'}; 
       }              }
               if ($metacache{$uri.'keys'}) {              if (defined($token->[2]->{'name'})) { 
                  $metacache{$uri.'keys'}.=','.$unikey;                  $unikey.='_'.$token->[2]->{'name'}; 
               } else {              }
                  $metacache{$uri.'keys'}=$unikey;              if ($metacache{$uri.'keys'}) {
       }                  $metacache{$uri.'keys'}.=','.$unikey;
               map {              } else {
   $metacache{$uri.''.$unikey.'.'.$_}=$token->[2]->{$_};                  $metacache{$uri.'keys'}=$unikey;
               } @{$token->[3]};              }
               unless (              foreach ( @{$token->[3]}) {
                  $metacache{$uri.''.$unikey}=$parser->get_text('/'.$entry)                  $metacache{$uri.''.$unikey.'.'.$_}=$token->[2]->{$_};
       ) { $metacache{$uri.''.$unikey}=              }
       $metacache{$uri.''.$unikey.'.default'};              if (! ($metacache{$uri.''.$unikey}=$parser->get_text('/'.$entry))){
       }                  $metacache{$uri.''.$unikey} = 
           }                      $metacache{$uri.''.$unikey.'.default'};
        }              }
           } # End of ($token->[0] eq 'S')
     }      }
     return \%metacache;      return \%metacache;
 }  }
   
 # ------------------------------------------------------------ Serves up a file  ########################################################
 # returns either the contents of the file or a -1  ########################################################
 sub getfile {  ###                                                  ###
   my $file=shift;  ###    Dynamic Metadata                              ###
   if (! -e $file ) { return -1; };  ###                                                  ###
   my $fh=IO::File->new($file);  ########################################################
   my $a='';  ########################################################
   while (<$fh>) { $a .=$_; }  ##
   return $a  ## Dynamic metadata description (incomplete)
   ##
   ## For a full description of all fields,
   ## see LONCAPA::lonmetadata
   ##
   ##   Field             Type
   ##-----------------------------------------------------------
   ##   count             integer
   ##   course            integer
   ##   course_list       comma separated list of course ids
   ##   avetries          real                                
   ##   avetries_list     comma separated list of real numbers
   ##   stdno             real
   ##   stdno_list        comma separated list of real numbers
   ##   usage             integer   
   ##   usage_list        comma separated list of resources
   ##   goto              scalar
   ##   goto_list         comma separated list of resources
   ##   comefrom          scalar
   ##   comefrom_list     comma separated list of resources
   ##   difficulty        real
   ##   difficulty_list   comma separated list of real numbers
   ##   sequsage          scalar
   ##   sequsage_list     comma separated list of resources
   ##   clear             real
   ##   technical         real
   ##   correct           real
   ##   helpful           real
   ##   depth             real
   ##   comments          html of all the comments made
   ##
   {
   
   my %DynamicData;
   my %Counts;
   
   sub process_dynamic_metadata {
       my ($user,$dom) = @_;
       undef(%DynamicData);
       undef(%Counts);
       #
       my $prodir = &propath($dom,$user);
       #
       # Read in the dynamic metadata
       my %evaldata;
       if (! tie(%evaldata,'GDBM_File',
                 $prodir.'/nohist_resevaldata.db',&GDBM_READER(),0640)) {
           return 0;
       }
       #
       %DynamicData = &LONCAPA::lonmetadata::process_reseval_data(\%evaldata);
       untie(%evaldata);
       $DynamicData{'domain'} = $dom;
       #print('user = '.$user.' domain = '.$dom.$/);
       #
       # Read in the access count data
       &log(7,'Reading access count data') if ($debug);
       my %countdata;
       if (! tie(%countdata,'GDBM_File',
                 $prodir.'/nohist_accesscount.db',&GDBM_READER(),0640)) {
           return 0;
       }
       while (my ($key,$count) = each(%countdata)) {
           next if ($key !~ /^$dom/);
           $key = &unescape($key);
           &log(8,'    Count '.$key.' = '.$count) if ($debug);
           $Counts{$key}=$count;
       }
       untie(%countdata);
       if ($debug) {
           &log(7,scalar(keys(%Counts)).
                " Counts read for ".$user."@".$dom);
           &log(7,scalar(keys(%DynamicData)).
                " Dynamic metadata read for ".$user."@".$dom);
       }
       #
       return 1;
 }  }
   
 # ------------------------------------------------------------- Declutters URLs  sub get_dynamic_metadata {
 sub declutter {      my ($url) = @_;
     my $thisfn=shift;      $url =~ s:^/res/::;
     $thisfn=~s/^$perlvar{'lonDocRoot'}//;      my %data = &LONCAPA::lonmetadata::process_dynamic_metadata($url,
     $thisfn=~s/^\///;                                                                 \%DynamicData);
     $thisfn=~s/^res\///;      # find the count
     return $thisfn;      $data{'count'} = $Counts{$url};
       #
       # Log the dynamic metadata
       if ($debug) {
           while (my($k,$v)=each(%data)) {
               &log(8,"    ".$k." => ".$v);
           }
       }
       return %data;
   }
   
   } # End of %DynamicData and %Counts scope
   
   ########################################################
   ########################################################
   ###                                                  ###
   ###   Counts                                         ###
   ###                                                  ###
   ########################################################
   ########################################################
   {
   
   my %countext;
   
   sub count_type {
       my $file=shift;
       $file=~/\.(\w+)$/;
       my $ext=lc($1);
       $countext{$ext}++;
   }
   
   sub write_type_count {
       open(RESCOUNT,'>/home/httpd/html/lon-status/rescount.txt');
       while (my ($extension,$count) = each(%countext)) {
    print RESCOUNT $extension.'='.$count.'&';
       }
       print RESCOUNT 'time='.time."\n";
       close(RESCOUNT);
   }
   
   } # end of scope for %countext
   
   {
   
   my %copyrights;
   
   sub count_copyright {
       $copyrights{@_[0]}++;
   }
   
   sub write_copyright_count {
       open(COPYCOUNT,'>/home/httpd/html/lon-status/copyrightcount.txt');
       while (my ($copyright,$count) = each(%copyrights)) {
    print COPYCOUNT $copyright.'='.$count.'&';
       }
       print COPYCOUNT 'time='.time."\n";
       close(COPYCOUNT);
 }  }
   
 # --------------------------------------- Is this the home server of an author?  } # end of scope for %copyrights
 # (copied from lond, modification of the return value)  
   ########################################################
   ########################################################
   ###                                                  ###
   ###   Miscellanous Utility Routines                  ###
   ###                                                  ###
   ########################################################
   ########################################################
   ##
   ## &ishome($username)
   ##   Returns 1 if $username is a LON-CAPA author, 0 otherwise
   ##   (copied from lond, modification of the return value)
 sub ishome {  sub ishome {
     my $author=shift;      my $author=shift;
     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;      $author=~s{/home/httpd/html/res/([^/]*)/([^/]*).*}{$1/$2};
     my ($udom,$uname)=split(/\//,$author);      my ($udom,$uname)=split(/\//,$author);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     if (-e $proname) {      if (-e $proname) {
Line 321  sub ishome { Line 827  sub ishome {
     }      }
 }  }
   
 # -------------------------------------------- Return path to profile directory  ##
 # (copied from lond)  ## &declutter($filename)
 sub propath {  ##   Given a filename, returns a url for the filename.
     my ($udom,$uname)=@_;  sub declutter {
     $udom=~s/\W//g;      my $thisfn=shift;
     $uname=~s/\W//g;      $thisfn=~s/^$Apache::lonnet::perlvar{'lonDocRoot'}//;
     my $subdir=$uname.'__';      $thisfn=~s/^\///;
     $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;      $thisfn=~s/^res\///;
     my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";      return $thisfn;
     return $proname;  
 }   
   
 # ---------------------------- convert 'time' format into a datetime sql format  
 sub sqltime {  
     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =  
  localtime(&unsqltime(@_[0]));  
     $mon++; $year+=1900;  
     return "$year-$mon-$mday $hour:$min:$sec";  
 }  
   
 sub maketime {  
     my %th=@_;  
     return POSIX::mktime(  
         ($th{'seconds'},$th{'minutes'},$th{'hours'},  
          $th{'day'},$th{'month'}-1,$th{'year'}-1900,0,0,$th{'dlsav'}));  
 }  
   
   
 #########################################  
 #  
 # Retro-fixing of un-backward-compatible time format  
   
 sub unsqltime {  
     my $timestamp=shift;  
     if ($timestamp=~/^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/) {  
        $timestamp=&maketime(  
    'year'=>$1,'month'=>$2,'day'=>$3,  
            'hours'=>$4,'minutes'=>$5,'seconds'=>$6);  
     }  
     return $timestamp;  
 }  }
   

Removed from v.1.24  
changed lines
  Added in v.1.82


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