File:  [LON-CAPA] / loncom / misc / rebuild_lastlogin.pl
Revision 1.1: download - view: text, annotated - select for diffs
Sat Oct 26 20:19:56 2013 UTC (10 years, 6 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0, HEAD
rebuild_lastlogin.pl is run on a library server and gathers
last login information for users in course(s) for which the current
server is the home server.

For each selected course a hash containing keys of:
username:domain:section:role with values: UNIX timestamp of
most recent role/section selection, i.e., "last log-in" will
be stored in a nohist_crslastlogin.db file for the course.

This script is intended to be run after installation of LON-CAPA
2.11.0 on a library server to populate nohist_crslastlogin.db files
for the domain's courses. (Thereafter role selection after log-in
on a server running 2.11.0 will update nohist_crslastlogin.db
when a course role is selected.)

This script might be run at other times. For example:

(a) if course user sessions are being routinely hosted on pre-2.11.0 servers.
(b) if nohist_crslastlogin.db is lost or corrupted, and needs to be rebuilt.

    1: #!/usr/bin/perl
    2: # The LearningOnline Network
    3: #
    4: # $Id: rebuild_lastlogin.pl,v 1.1 2013/10/26 20:19:56 raeburn Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: #################################################
   29: 
   30: =pod
   31: 
   32: =head1 NAME
   33: 
   34: rebuild_lastlogin.pl
   35: 
   36: =head1 SYNOPSIS
   37: 
   38: rebuild_lastlogin.pl is run on a library server and gathers
   39: last login information for users in course(s) for which the current 
   40: server is the home server.
   41: 
   42: =head1 DESCRIPTION
   43: 
   44: For each selected course a hash containing keys of: 
   45: username:domain:section:role with values: UNIX timestamp of
   46: most recent role/section selection, i.e., "last log-in" will
   47: be stored in a nohist_crslastlogin.db file for the course.
   48: 
   49: usage: either enter the script name followed by at least one argument:
   50: the time interval prior to now, i.e., day, week, month, year or
   51: all for which last log-in activity is to be retrieved  or enter the 
   52: script name followed by at least two arguments:
   53: 1. a specific courseID and 2. a specific course domain.
   54: 
   55: Both cases support an additional, optional, final argument: 
   56: a two letter code for the desired language.
   57: 
   58:                  ar => ﺎﻠﻋﺮﺒﻳﺓ
   59:                  de => Deutsch
   60:                  en => English
   61:                  es => español
   62:                  fa => ﺍیﺭﺎﻧی
   63:                  fr => français
   64:                  he => עברית
   65:                  ja => 日本語
   66:                  pt => Português
   67:                  ru => Русский
   68:                  tr => türkçe
   69:                  zh => 简体中文
   70: 
   71: The default language is en.  If a translation exists for the language
   72: specified as the final argument, then if a time interval is being used
   73: that will be specified in that language, in the first argument when 
   74: calling the script. 
   75: 
   76: The script must be run as the user www.
   77: 
   78: Because the script will attempt to retrieve user activity log from
   79: the home server of each student or course personnel, the LON-CAPA
   80: daemons need to be running in case that person's home server is not
   81: the same as the course's home server.
   82: 
   83: =cut
   84: 
   85: #################################################
   86: 
   87: #! /usr/bin/perl
   88: 
   89: use strict;
   90: use lib '/home/httpd/lib/perl/';
   91: use Apache::lonnet;
   92: use Apache::loncommon;
   93: use Apache::loncoursedata;
   94: use Apache::lonlocal;
   95: use LONCAPA qw(:DEFAULT :match);
   96: 
   97: my %languages = (
   98:                   ar => 'ﺎﻠﻋﺮﺒﻳﺓ',
   99:                   de => 'Deutsch',
  100:                   en => 'English',
  101:                   es => 'español',
  102:                   fa => 'ﺍیﺭﺎﻧی',
  103:                   fr => 'français',
  104:                   he => 'עברית',
  105:                   ja => '日本語',
  106:                   pt => 'Português',
  107:                   ru => 'Русский',
  108:                   tr => 'türkçe',
  109:                   zh => '简体中文',
  110:                 );
  111: my $langchoices = "\n";
  112: foreach my $key (sort(keys(%languages))) {
  113:     $langchoices .= '                 '.$key.' => '.$languages{$key}."\n";
  114: }
  115: my $lang = 'en';
  116: my @args = @ARGV;
  117: 
  118: my @domains = sort(&Apache::lonnet::current_machine_domains());
  119: my @ids=&Apache::lonnet::current_machine_ids();
  120: my ($domfilter,$possdomstr);
  121: my $crsfilter = '.';
  122: if (@domains > 1) {
  123:     $possdomstr = join('|',@domains);
  124: } else {
  125:     $possdomstr = $domains[0];
  126: }
  127: if (@args>3) {
  128:     if (exists($languages{$args[2]})) {
  129:         $lang = $args[2];
  130:     }
  131:     &Apache::lonlocal::get_language_handle(undef,$lang);
  132:     print &mt('[_1] takes a maximum of 3 arguments.','rebuild_lastlogin.pl')."\n";
  133:     exit; 
  134: } elsif ((@args==3) && exists($languages{$args[2]})) {
  135:     $lang = pop(@args);
  136: } elsif ((@args==2) && exists($languages{$args[1]})) {
  137:     $lang = pop(@args);
  138: }
  139: &Apache::lonlocal::get_language_handle(undef,$lang);
  140: if (@args == 2) { 
  141:     my ($cnum,$cdom) = @args;
  142:     my $invalid;
  143:     if ($cnum =~ /^$match_courseid$/ && $cdom =~ /^$match_domain$/) {
  144:         if (grep(/^\Q$cdom\E$/,@domains)) {
  145:             my %crshash = &Apache::lonnet::coursedescription("$cdom/$cnum",{'one_time' => 1});
  146:             if ($crshash{'num'} eq $cnum) {
  147:                 $crsfilter = $cnum;
  148:                 $domfilter = $cdom;
  149:             } else {
  150:                 $invalid = 1;
  151:             }
  152:         } else {
  153:             $invalid = 1;
  154:         }
  155:     } else {
  156:         $invalid = 1;
  157:     }
  158:     if ($invalid) {
  159:         print "\n".&mt('usage for a single course: [_1] where [_2] is an optional two letter code.',
  160:                   'rebuild_lastlogin.pl [COURSEID] [COURSEDOMAIN] <LANG>',
  161:                   '<LANG>')."\n";
  162:         exit;
  163:     }
  164: }
  165: if ($crsfilter eq '.') {
  166:     if (!@args || ($args[0] ne &mt('all') && $args[0] ne &mt('year') && $args[0] ne &mt('month') &&
  167:                    $args[0] ne &mt('week') && $args[0] ne &mt('day'))) {
  168:         print "\n".
  169:               &mt('usage: either enter the script name followed by at least one argument')."\n".
  170:                   '       -- 1. '.&mt('the time interval from prior to now, i.e., day, week etc., for which log-in activity is to be checked')."\n\n".
  171:                   '      '.&mt('or enter the script name followed by at least two arguments')."\n".
  172:                   '        -- 1. '.&mt('a specific courseID')."\n".
  173:                   '        -- 2. '.&mt('a specific course domain')."\n\n".
  174:                   '              '.&mt('Both cases support an additional, optional, final argument: a two letter code for the desired language')."\n".
  175:                   '              '.&mt('-- one of: [_1]').$langchoices."\n".
  176:                   '      '.&mt('Accordingly use one of the following:')."\n\n".
  177:                   '      rebuild_lastlogin.pl '.&mt('all').' <LANG>'."\n".
  178:                   '      rebuild_lastlogin.pl '.&mt('year').' <LANG>'."\n".
  179:                   '      rebuild_lastlogin.pl '.&mt('month').' <LANG>'."\n".
  180:                   '      rebuild_lastlogin.pl '.&mt('week').' <LANG>'."\n".
  181:                   '      rebuild_lastlogin.pl '.&mt('day').' <LANG>'."\n".
  182:                   '      rebuild_lastlogin.pl [COURSEID] [COURSEDOMAIN] <LANG> '."\n";
  183:         exit;
  184:     }
  185: }
  186: 
  187: #  Make sure this process is running from user=www
  188: my $wwwid=getpwnam('www');
  189: if ($wwwid!=$<) {
  190:     my $emailto="$Apache::lonnet::perlvar{'lonAdmEMail'}}";
  191:     my $subj="LON: $Apache::lonnet::perlvar{'lonHostID'} User ID mismatch";
  192:     my $msg=&mt('User ID mismatch.').' '.&mt('[_1] must be run as user www.','rebuild_lastlogin.pl');
  193:     system("echo '$msg' | mail -s '$subj' $emailto > /dev/null");
  194:     exit 1;
  195: }
  196: 
  197: if ($Apache::lonnet::perlvar{'lonRole'} ne 'library') {
  198:     print &mt('[_1] only runs on a LON-CAPA library server.','rebuild_lastlogin.pl')."\n";
  199:     exit;  
  200: }
  201: 
  202: # Log script run
  203: open(my $fh,'>>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/logs/buildlastlogin.log');
  204: print $fh "==== buildlastlogindb.pl Run ".localtime()."====\n";
  205: 
  206: my %users;
  207: my $timefilter = 1;
  208: my $now = time;
  209: if ($crsfilter eq '.') {
  210:     if ($ARGV[0] eq &mt('year')) {
  211:         $timefilter = $now-31536000;
  212:     } elsif ($ARGV[0] eq &mt('month')) {
  213:         $timefilter = $now-2592000;
  214:     } elsif ($ARGV[0] eq &mt('week')) {
  215:         $timefilter = $now-604800;
  216:     } elsif ($ARGV[0] eq &mt('day')) {
  217:         $timefilter = $now-86400;
  218:     }
  219: }
  220: 
  221: foreach my $dom (@domains) {
  222:     if ($domfilter) {
  223:         next if ($dom ne $domfilter);
  224:     }
  225:     my %courseshash;
  226:     my %currhash = &Apache::lonnet::courseiddump($dom,'.',$timefilter,'.','.',$crsfilter,1,\@ids,'.');
  227:     foreach my $cid (sort(keys(%currhash))) {
  228:         my ($cdom,$cnum) = split(/_/,$cid);
  229:         my $path = &propath($cdom,$cnum);
  230:         my %advrolehash = &Apache::lonnet::get_my_roles($cnum,$cdom,undef,
  231:                               ['previous','active','future']);
  232:         my %coursehash = &Apache::lonnet::coursedescription("$cdom/$cnum",{'one_time' => 1});
  233:         my %nothidden;
  234:         if ($coursehash{'nothideprivileged'}) {
  235:             foreach my $item (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) {
  236:                 my $user;
  237:                 if ($item =~ /:/) {
  238:                     $user = $item;
  239:                 } else {
  240:                     $user = join(':',split(/[\@]/,$item));
  241:                 }
  242:                 $nothidden{$user} = 1;
  243:             }
  244:         }
  245:         foreach my $user (keys(%advrolehash)) {
  246:             my ($uname,$udom,$rest) = split(/:/,$user,3);
  247:             next if ($users{$uname.':'.$udom});
  248:             my @privdoms = ($cdom);
  249:             if ($udom ne $cdom) {
  250:                 @privdoms = ($udom,$cdom);
  251:             }
  252:             if (&Apache::lonnet::privileged($uname,$udom,\@privdoms)) {
  253:                 unless ($nothidden{$uname.':'.$udom}) {
  254:                     next;
  255:                 }
  256:             }
  257:             $users{$uname.':'.$udom} = 1;
  258:         }
  259:         my $classlist=&Apache::loncoursedata::get_classlist($cdom,$cnum);
  260:         if (ref($classlist) eq 'HASH') {
  261:             foreach my $student (keys(%{$classlist})) {
  262:                 next if ($users{$student});
  263:                 if ($student =~/^($match_username)\:($match_domain)$/) {
  264:                     my ($tuname,$tudom)=($1,$2);
  265:                     my @privdoms = ($cdom);
  266:                     if ($tudom ne $cdom) {
  267:                         @privdoms = ($tudom,$cdom);
  268:                     }
  269:                     if (&Apache::lonnet::privileged($tuname,$tudom,\@privdoms)) {
  270:                         unless ($nothidden{$student}) {
  271:                             next;
  272:                         }
  273:                     }
  274:                     $users{$student} = 1;
  275:                 }
  276:             }
  277:         }
  278:     }
  279: }
  280: 
  281: my %lastlogin;
  282: my (%numlib,%domservers,%filter);
  283: $filter{'action'} = 'Role';
  284: my $possdom;
  285: if ($domfilter) {
  286:     $possdom = $domfilter;
  287: } else {
  288:     $possdom = $possdomstr;
  289: }
  290: foreach my $dom (@domains) {
  291:     my %servers = &Apache::lonnet::get_servers($dom,'library');
  292:     $numlib{$dom} = scalar(keys(%servers));
  293:     if ($numlib{$dom} > 1) {
  294:         $domservers{$dom} = %servers;
  295:     }
  296: }
  297: foreach my $user (sort(keys(%users))) {
  298:     my ($uname,$udom) = split(/:/,$user);
  299:     if (grep(/^\Q$udom\E$/,@domains)) {
  300:         if ($numlib{$udom} == 1) {
  301:             &readlocalactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%lastlogin);
  302:         } else {
  303:             if (ref($domservers{$udom}) eq 'HASH') {
  304:                 my $uhome = &Apache::lonnet::homeserver($uname,$udom);
  305:                 if ($uhome ne 'no_host') {
  306:                     if ($domservers{$udom}{$uhome}) {
  307:                         &readlocalactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%lastlogin);
  308:                     } else {
  309:                         &readremoteactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%filter,
  310:                                                \%lastlogin);
  311:                     }
  312:                 }
  313:             }
  314:         }
  315:     } else {
  316:         my $uhome = &Apache::lonnet::homeserver($uname,$udom);
  317:         if ($uhome ne 'no_host') {
  318:             &readremoteactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%filter,\%lastlogin);
  319:         }
  320:     }
  321: }
  322: 
  323: foreach my $key (sort(keys(%lastlogin))) {
  324:     if (ref($lastlogin{$key}) eq 'HASH') {
  325:         if ($key =~ /^($match_domain)_($match_courseid)$/) {
  326:             my $cdom = $1;
  327:             my $cnum = $2;
  328:             my $putresult = &Apache::lonnet::put('nohist_crslastlogin',$lastlogin{$key},
  329:                                                  $cdom,$cnum);
  330:             if ($putresult eq 'ok') {
  331:                 print $fh "stored last login data for $key\n";
  332:             }
  333:         }
  334:     }
  335: }
  336: 
  337: ## Finished!
  338: print $fh "==== buildlastlogindb.pl completed ".localtime()." ====\n";
  339: close($fh);
  340: exit;
  341: 
  342: sub readlocalactivitylog {
  343:     my($uname,$udom,$possdomstr,$timefilter,$crsfilter,$lastlogin) = @_;
  344:     my $path = &propath($udom,$uname);
  345:     my $posscnum;
  346:     if ($crsfilter eq '.') {
  347:         $posscnum = $match_courseid;
  348:     } else {
  349:         $posscnum = $crsfilter;
  350:     } 
  351:     if (-e "$path/activity.log") {
  352:         if (open(my $fh,"<$path/activity.log")) {
  353:             my @lines = <$fh>;
  354:             @lines = reverse(@lines);
  355:             foreach my $line (@lines) {
  356:                 chomp($line);
  357:                 if ($line =~ m{^(\d+):\w+:Role\s+(cc|in|ta|ep|st|ad)\./($possdomstr)/($posscnum)/?([^/]*)}) {
  358:                     if (($timefilter > 1) && ($1<$timefilter)) {
  359:                         last;
  360:                     }
  361:                     if (ref($lastlogin->{$3.'_'.$4}) eq 'HASH') {
  362:                         if ($lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2}) {
  363:                             if ($crsfilter ne '.') {
  364:                                 last;
  365:                             } else {
  366:                                 next;
  367:                             }
  368:                         }
  369:                     }
  370:                     $lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2} = $1;
  371:                 }
  372:             }
  373:             close($fh);
  374:         }
  375:     }
  376:     return;
  377: }
  378: 
  379: sub readremoteactivitylog {
  380:     my ($uname,$udom,$possdomstr,$timefilter,$crsfilter,$filter,$lastlogin) = @_;
  381:     if (ref($filter) eq 'HASH') {
  382:         my %filters = %{$filter};
  383:         my $result = &Apache::lonnet::userlog_query($uname,$udom,%filters);
  384:         my $posscnum;
  385:         if ($crsfilter eq '.') {
  386:             $posscnum = $match_courseid;
  387:         } else {
  388:             $posscnum = $crsfilter;
  389:         }
  390:         my $now = time;
  391:         if (($result ne 'file_error') && ($result ne 'error: reply_file_error') && ($result !~ /^timeout:/)) {
  392:             my $now = time;
  393:             foreach my $item (map { &unescape($_); } (split(/&/,$result))) {
  394:                 if ($item =~ m{^(\d+):\w+:Role\s+(cc|in|ta|ep|st|ad)\./($possdomstr)/($posscnum)/?([^/]*)}) {
  395:                     if (($timefilter > 1) && ($1<$timefilter)) {
  396:                         last;
  397:                     }
  398:                     if (ref($lastlogin->{$3.'_'.$4}) eq 'HASH') {
  399:                         if ($lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2}) {
  400:                             if ($crsfilter ne '.') {
  401:                                 last;
  402:                             } else {
  403:                                 next;
  404:                             }
  405:                         }
  406:                     }
  407:                     $lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2} = $1;
  408:                 }
  409:             }
  410:         }
  411:     }
  412:     return;
  413: }
  414: 

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