Annotation of loncom/Lond.pm, revision 1.3
1.1 droeschl 1: # The LearningOnline Network
2: #
1.3 ! droeschl 3: # $Id: Lond.pm,v 1.2 2012/04/26 19:51:40 droeschl Exp $
1.1 droeschl 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: ###
28:
29: #NOTE perldoc at the end of file
30:
31: package LONCAPA::Lond;
32:
33: use strict;
34: use lib '/home/httpd/lib/perl/';
35:
36: use LONCAPA;
37: use Apache::lonnet;
38: use GDBM_File;
39:
40:
41: sub dump_with_regexp {
1.2 droeschl 42: my ( $tail, $clientname, $clientversion ) = @_;
43: my ( $udom, $uname, $namespace, $regexp, $range ) =
44: split /:/, $tail;
1.1 droeschl 45:
1.2 droeschl 46: $regexp = defined $regexp ? unescape($regexp) : '.';
1.1 droeschl 47:
48: my ($start,$end);
1.2 droeschl 49:
1.1 droeschl 50: if (defined($range)) {
1.2 droeschl 51: if ($range =~ /^(\d+)\-(\d+)$/) {
52: ($start,$end) = ($1,$2);
53: } elsif ($range =~/^(\d+)$/) {
54: ($start,$end) = (0,$1);
55: } else {
56: undef($range);
57: }
58: }
59:
60: my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()) or
61: return "error: ".($!+0)." tie(GDBM) Failed while attempting dump";
62:
63: my $qresult = '';
64: my $count = 0;
1.1 droeschl 65: #
66: # When dump is for roles.db, determine if LON-CAPA version checking is needed.
1.2 droeschl 67: # Sessions on 2.10 and later do not require version checking, as that occurs
1.1 droeschl 68: # on the server hosting the user session, when constructing the roles/courses
69: # screen).
70: #
1.2 droeschl 71: my $skipcheck;
72: my @ids = &Apache::lonnet::current_machine_ids();
73: my (%homecourses, $major, $minor, $now);
1.1 droeschl 74: #
75: # If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA
1.2 droeschl 76: # version on the server which requested the data.
1.1 droeschl 77: #
1.2 droeschl 78: if ($namespace eq 'roles') {
79: if ($clientversion =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?/) {
80: $major = $1;
81: $minor = $2;
82: }
83: if (($major > 2) || (($major == 2) && ($minor > 9))) {
84: $skipcheck = 1;
1.1 droeschl 85: }
1.2 droeschl 86: $now = time;
87: }
88: while (my ($key,$value) = each(%$hashref)) {
89: if ($namespace eq 'roles' && (!$skipcheck)) {
1.1 droeschl 90: if ($key =~ m{^/($LONCAPA::match_domain)/($LONCAPA::match_courseid)(/?[^_]*)_(cc|co|in|ta|ep|ad|st|cr)$}) {
91: my $cdom = $1;
92: my $cnum = $2;
1.2 droeschl 93: my ($role,$roleend,$rolestart) = split(/\_/,$value);
94: if (!$roleend || $roleend > $now) {
1.1 droeschl 95: #
96: # For active course roles, check that requesting server is running a LON-CAPA
97: # version which meets any version requirements for the course. Do not include
98: # the role amongst the results returned if the requesting server's version is
99: # too old.
100: #
101: # This determination is handled differently depending on whether the course's
102: # homeserver is the current server, or whether it is a different server.
103: # In both cases, the course's version requirement needs to be retrieved.
104: #
1.2 droeschl 105: next unless (&releasereqd_check($cnum,$cdom,$key,$value,$major,
106: $minor,\%homecourses,\@ids));
1.1 droeschl 107: }
108: }
109: }
1.2 droeschl 110: if ($regexp eq '.') {
111: $count++;
112: if (defined($range) && $count >= $end) { last; }
113: if (defined($range) && $count < $start) { next; }
114: $qresult.=$key.'='.$value.'&';
115: } else {
116: my $unescapeKey = &unescape($key);
117: if (eval('$unescapeKey=~/$regexp/')) {
118: $count++;
119: if (defined($range) && $count >= $end) { last; }
120: if (defined($range) && $count < $start) { next; }
121: $qresult.="$key=$value&";
122: }
123: }
124: }
125:
126: &untie_user_hash($hashref) or
127: return "error: ".($!+0)." untie(GDBM) Failed while attempting dump";
1.1 droeschl 128: #
129: # If dump is for roles.db from a pre-2.10 server, check if the LON-CAPA
130: # version requirements for courses for which the current server is the home
131: # server permit course roles to be usable on the client server hosting the
132: # user's session. If so, include those role results in the data returned to
133: # the client server.
134: #
1.2 droeschl 135: if (($namespace eq 'roles') && (!$skipcheck)) {
136: if (keys(%homecourses) > 0) {
137: $qresult .= &check_homecourses(\%homecourses,$regexp,$count,
138: $range,$start,$end,$major,$minor);
139: }
140: }
141: chop($qresult);
142: return $qresult;
143: }
144:
145:
146: sub releasereqd_check {
147: my ($cnum,$cdom,$key,$value,$major,$minor,$homecourses,$ids) = @_;
148: my $home = &Apache::lonnet::homeserver($cnum,$cdom);
149: return if ($home eq 'no_host');
150: my ($reqdmajor,$reqdminor,$displayrole);
151: if ($cnum =~ /$LONCAPA::match_community/) {
152: if ($major eq '' && $minor eq '') {
153: return unless ((ref($ids) eq 'ARRAY') &&
154: (grep(/^\Q$home\E$/,@{$ids})));
155: } else {
156: $reqdmajor = 2;
157: $reqdminor = 9;
158: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor));
159: }
160: }
161: my $hashid = $cdom.':'.$cnum;
162: my ($courseinfo,$cached) =
163: &Apache::lonnet::is_cached_new('courseinfo',$hashid);
164: if (defined($cached)) {
165: if (ref($courseinfo) eq 'HASH') {
166: if (exists($courseinfo->{'releaserequired'})) {
167: my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'});
168: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor));
169: }
170: }
171: } else {
172: if (ref($ids) eq 'ARRAY') {
173: if (grep(/^\Q$home\E$/,@{$ids})) {
174: if (ref($homecourses) eq 'HASH') {
175: if (ref($homecourses->{$cdom}) eq 'HASH') {
176: if (ref($homecourses->{$cdom}{$cnum}) eq 'HASH') {
177: if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') {
178: push(@{$homecourses->{$cdom}{$cnum}},{$key=>$value});
179: } else {
180: $homecourses->{$cdom}{$cnum} = [{$key=>$value}];
181: }
182: } else {
183: $homecourses->{$cdom}{$cnum} = [{$key=>$value}];
184: }
185: } else {
186: $homecourses->{$cdom}{$cnum} = [{$key=>$value}];
187: }
188: }
189: return;
190: }
191: }
192: my $courseinfo = &get_courseinfo_hash($cnum,$cdom,$home);
193: if (ref($courseinfo) eq 'HASH') {
194: if (exists($courseinfo->{'releaserequired'})) {
195: my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'});
196: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor));
197: }
198: } else {
199: return;
200: }
201: }
202: return 1;
203: }
204:
205:
206: sub check_homecourses {
207: my ($homecourses,$regexp,$count,$range,$start,$end,$major,$minor) = @_;
208: my ($result,%addtocache);
209: my $yesterday = time - 24*3600;
210: if (ref($homecourses) eq 'HASH') {
211: my (%okcourses,%courseinfo,%recent);
212: foreach my $domain (keys(%{$homecourses})) {
213: my $hashref =
214: &tie_domain_hash($domain, "nohist_courseids", &GDBM_WRCREAT());
215: if (ref($hashref) eq 'HASH') {
216: while (my ($key,$value) = each(%$hashref)) {
217: my $unesc_key = &unescape($key);
218: if ($unesc_key =~ /^lasttime:(\w+)$/) {
219: my $cid = $1;
220: $cid =~ s/_/:/;
221: if ($value > $yesterday ) {
222: $recent{$cid} = 1;
223: }
224: next;
225: }
226: my $items = &Apache::lonnet::thaw_unescape($value);
227: if (ref($items) eq 'HASH') {
228: my ($cdom,$cnum) = split(/_/,$unesc_key);
229: my $hashid = $cdom.':'.$cnum;
230: $courseinfo{$hashid} = $items;
231: if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') {
232: my ($reqdmajor,$reqdminor) = split(/\./,$items->{'releaserequired'});
233: if (&useable_role($reqdmajor,$reqdminor,$major,$minor)) {
234: $okcourses{$hashid} = 1;
235: }
236: }
237: }
238: }
239: unless (&untie_domain_hash($hashref)) {
240: &logthis("Failed to untie tied hash for nohist_courseids.db for $domain");
241: }
242: } else {
243: &logthis("Failed to tie hash for nohist_courseids.db for $domain");
244: }
245: }
246: foreach my $hashid (keys(%recent)) {
247: my ($result,$cached)=&Apache::lonnet::is_cached_new('courseinfo',$hashid);
248: unless ($cached) {
249: &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600);
250: }
251: }
252: foreach my $cdom (keys(%{$homecourses})) {
253: if (ref($homecourses->{$cdom}) eq 'HASH') {
254: foreach my $cnum (keys(%{$homecourses->{$cdom}})) {
255: my $hashid = $cdom.':'.$cnum;
256: next if ($recent{$hashid});
257: &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600);
258: }
259: }
260: }
261: foreach my $hashid (keys(%okcourses)) {
262: my ($cdom,$cnum) = split(/:/,$hashid);
263: if ((ref($homecourses->{$cdom}) eq 'HASH') &&
264: (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY')) {
265: foreach my $role (@{$homecourses->{$cdom}{$cnum}}) {
266: if (ref($role) eq 'HASH') {
267: while (my ($key,$value) = each(%{$role})) {
268: if ($regexp eq '.') {
269: $count++;
270: if (defined($range) && $count >= $end) { last; }
271: if (defined($range) && $count < $start) { next; }
272: $result.=$key.'='.$value.'&';
273: } else {
274: my $unescapeKey = &unescape($key);
275: if (eval('$unescapeKey=~/$regexp/')) {
276: $count++;
277: if (defined($range) && $count >= $end) { last; }
278: if (defined($range) && $count < $start) { next; }
279: $result.="$key=$value&";
280: }
281: }
282: }
283: }
1.1 droeschl 284: }
285: }
1.2 droeschl 286: }
1.1 droeschl 287: }
1.2 droeschl 288: return $result;
289: }
290:
1.1 droeschl 291:
1.2 droeschl 292: sub useable_role {
293: my ($reqdmajor,$reqdminor,$major,$minor) = @_;
294: if ($reqdmajor ne '' && $reqdminor ne '') {
295: return if (($major eq '' && $minor eq '') ||
296: ($major < $reqdmajor) ||
297: (($major == $reqdmajor) && ($minor < $reqdminor)));
298: }
1.1 droeschl 299: return 1;
300: }
301:
1.2 droeschl 302:
1.3 ! droeschl 303: sub get_courseinfo_hash {
! 304: my ($cnum,$cdom,$home) = @_;
! 305: my %info;
! 306: eval {
! 307: local($SIG{ALRM}) = sub { die "timeout\n"; };
! 308: local($SIG{__DIE__})='DEFAULT';
! 309: alarm(3);
! 310: %info = &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,1,[$home],'.');
! 311: alarm(0);
! 312: };
! 313: if ($@) {
! 314: if ($@ eq "timeout\n") {
! 315: &logthis("<font color='blue'>WARNING courseiddump for $cnum:$cdom from $home timedout</font>");
! 316: } else {
! 317: &logthis("<font color='yellow'>WARNING unexpected error during eval of call for courseiddump from $home</font>");
! 318: }
! 319: } else {
! 320: if (ref($info{$cdom.'_'.$cnum}) eq 'HASH') {
! 321: my $hashid = $cdom.':'.$cnum;
! 322: return &Apache::lonnet::do_cache_new('courseinfo',$hashid,$info{$cdom.'_'.$cnum},600);
! 323: }
! 324: }
! 325: return;
! 326: }
1.2 droeschl 327:
328:
329:
330:
1.1 droeschl 331: 1;
332:
333: __END__
334:
335: =head1 NAME
336:
337: LONCAPA::Lond.pm
338:
339: =head1 SYNOPSIS
340:
341: #TODO
342:
343: =head1 DESCRIPTION
344:
345: #TODO
346:
347: =head1 METHODS
348:
349: =over 4
350:
1.2 droeschl 351: =item dump_with_regexp( $tail, $client )
1.1 droeschl 352:
353: Dump a profile database with an optional regular expression to match against
354: the keys. In this dump, no effort is made to separate symb from version
355: information. Presumably the databases that are dumped by this command are of a
356: different structure. Need to look at this and improve the documentation of
357: both this and the currentdump handler.
358:
359: $tail a colon separated list containing
360:
361: =over
362:
363: =item domain
364:
365: =item user
366:
367: identifying the user.
368:
369: =item namespace
370:
371: identifying the database.
372:
373: =item regexp
374:
375: optional regular expression that is matched against database keywords to do
376: selective dumps.
377:
378: =item range
379:
380: optional range of entries e.g., 10-20 would return the 10th to 19th items, etc.
381:
382: =back
383:
384: $client is the channel open on the client.
385:
386: Returns: 1 (Continue processing).
387:
388: Side effects: response is written to $client.
389:
1.2 droeschl 390:
391: =item releasereqd_check( $cnum, $cdom, $key, $value, $major, $minor,
392: $homecourses, $ids )
393:
394: releasereqd_check() will determine if a LON-CAPA version (defined in the
395: $major,$minor args passed) is not too old to allow use of a role in a
396: course ($cnum,$cdom args passed), if at least one of the following applies:
397: (a) the course is a Community, (b) the course's home server is *not* the
398: current server, or (c) cached course information is not stale.
399:
400: For the case where none of these apply, the course is added to the
401: $homecourse hash ref (keys = courseIDs, values = array of a hash of roles).
402: The $homecourse hash ref is for courses for which the current server is the
403: home server. LON-CAPA version requirements are checked elsewhere for the
404: items in $homecourse.
405:
406:
407: =item check_homecourses( $homecourses, $regexp, $count, $range, $start, $end,
408: $major, $minor )
409:
410: check_homecourses() will retrieve course information for those courses which
411: are keys of the $homecourses hash ref (first arg). The nohist_courseids.db
412: GDBM file is tied and course information for each course retrieved. Last
413: visit (lasttime key) is also retrieved for each, and cached values updated
414: for any courses last visited less than 24 hours ago. Cached values are also
415: updated for any courses included in the $homecourses hash ref.
416:
417: The reason for the 24 hours constraint is that the cron entry in
418: /etc/cron.d/loncapa for /home/httpd/perl/refresh_courseids_db.pl causes
419: cached course information to be updated nightly for courses with activity
420: within the past 24 hours.
421:
422: Role information for the user (included in a ref to an array of hashes as the
423: value for each key in $homecourses) is appended to the result returned by the
424: routine, which will in turn be appended to the string returned to the client
425: hosting the user's session.
426:
427:
428: =item useable_role( $reqdmajor, $reqdminor, $major, $minor )
429:
430: useable_role() will compare the LON-CAPA version required by a course with
431: the version available on the client server. If the client server's version
432: is compatible, 1 will be returned.
433:
434:
1.3 ! droeschl 435: =item get_courseinfo_hash( $cnum, $cdom, $home )
! 436:
! 437: get_courseinfo_hash() is used to retrieve course information from the db
! 438: file: nohist_courseids.db for a course for which the current server is *not*
! 439: the home server.
! 440:
! 441: A hash of a hash will be retrieved. The outer hash contains a single key --
! 442: courseID -- for the course for which the data are being requested.
! 443: The contents of the inner hash, for that single item in the outer hash
! 444: are returned (and cached in memcache for 10 minutes).
! 445:
! 446:
! 447:
1.1 droeschl 448: =back
449:
450: =head1 BUGS
451:
452: No known bugs at this time.
453:
454: =head1 SEE ALSO
455:
456: L<Apache::lonnet>, L<lond>
457:
458: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>