Annotation of loncom/metadata_database/searchcat.pl, revision 1.63
1.1 harris41 1: #!/usr/bin/perl
2: # The LearningOnline Network
3: # searchcat.pl "Search Catalog" batch script
1.16 harris41 4: #
1.63 ! matthew 5: # $Id: searchcat.pl,v 1.62 2005/03/11 03:25:18 matthew Exp $
1.16 harris41 6: #
7: # Copyright Michigan State University Board of Trustees
8: #
1.29 albertel 9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
1.16 harris41 10: #
1.29 albertel 11: # LON-CAPA is free software; you can redistribute it and/or modify
1.16 harris41 12: # it under the terms of the GNU General Public License as published by
13: # the Free Software Foundation; either version 2 of the License, or
14: # (at your option) any later version.
15: #
1.29 albertel 16: # LON-CAPA is distributed in the hope that it will be useful,
1.16 harris41 17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19: # GNU General Public License for more details.
20: #
21: # You should have received a copy of the GNU General Public License
1.29 albertel 22: # along with LON-CAPA; if not, write to the Free Software
1.16 harris41 23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24: #
25: # /home/httpd/html/adm/gpl.txt
26: #
1.29 albertel 27: # http://www.lon-capa.org/
1.16 harris41 28: #
29: ###
1.33 matthew 30:
1.32 www 31: =pod
1.1 harris41 32:
1.32 www 33: =head1 NAME
34:
35: B<searchcat.pl> - put authoritative filesystem data into sql database.
36:
37: =head1 SYNOPSIS
38:
39: Ordinarily this script is to be called from a loncapa cron job
40: (CVS source location: F<loncapa/loncom/cron/loncapa>; typical
41: filesystem installation location: F</etc/cron.d/loncapa>).
42:
43: Here is the cron job entry.
44:
45: C<# Repopulate and refresh the metadata database used for the search catalog.>
46: C<10 1 * * 7 www /home/httpd/perl/searchcat.pl>
47:
48: This script only allows itself to be run as the user C<www>.
49:
50: =head1 DESCRIPTION
51:
52: This script goes through a loncapa resource directory and gathers metadata.
53: The metadata is entered into a SQL database.
54:
55: This script also does general database maintenance such as reformatting
56: the C<loncapa:metadata> table if it is deprecated.
57:
58: This script evaluates dynamic metadata from the authors'
1.48 www 59: F<nohist_resevaldata.db> database file in order to store it in MySQL.
1.32 www 60:
61: This script is playing an increasingly important role for a loncapa
62: library server. The proper operation of this script is critical for a smooth
63: and correct user experience.
64:
65: =cut
1.1 harris41 66:
1.45 www 67: use strict;
68:
1.55 matthew 69: use DBI;
1.17 harris41 70: use lib '/home/httpd/lib/perl/';
1.55 matthew 71: use LONCAPA::lonmetadata;
1.17 harris41 72:
1.56 matthew 73: use Getopt::Long;
1.1 harris41 74: use IO::File;
75: use HTML::TokeParser;
1.21 www 76: use GDBM_File;
1.24 www 77: use POSIX qw(strftime mktime);
1.56 matthew 78:
1.63 ! matthew 79: use Apache::lonnet();
1.62 matthew 80:
1.55 matthew 81: use File::Find;
1.1 harris41 82:
1.56 matthew 83: #
84: # Set up configuration options
1.63 ! matthew 85: my ($simulate,$oneuser,$help,$verbose,$logfile,$debug);
1.56 matthew 86: GetOptions (
87: 'help' => \$help,
88: 'simulate' => \$simulate,
89: 'only=s' => \$oneuser,
90: 'verbose=s' => \$verbose,
91: 'debug' => \$debug,
92: );
93:
94: if ($help) {
95: print <<"ENDHELP";
96: $0
97: Rebuild and update the LON-CAPA metadata database.
98: Options:
99: -help Print this help
100: -simulate Do not modify the database.
101: -only=user Only compute for the given user. Implies -simulate
102: -verbose=val Sets logging level, val must be a number
103: -debug Turns on debugging output
104: ENDHELP
105: exit 0;
106: }
107:
108: if (! defined($debug)) {
109: $debug = 0;
110: }
111:
112: if (! defined($verbose)) {
113: $verbose = 0;
114: }
115:
116: if (defined($oneuser)) {
117: $simulate=1;
118: }
119:
1.55 matthew 120: ##
121: ## Use variables for table names so we can test this routine a little easier
122: my $oldname = 'metadata';
1.59 matthew 123: my $newname = 'newmetadata'.$$; # append pid to have unique temporary table
1.45 www 124:
1.55 matthew 125: #
126: # Only run if machine is a library server
1.63 ! matthew 127: exit if ($Apache::lonnet::perlvar{'lonRole'} ne 'library');
1.55 matthew 128: #
129: # Make sure this process is running from user=www
130: my $wwwid=getpwnam('www');
131: if ($wwwid!=$<) {
1.63 ! matthew 132: my $emailto="$Apache::lonnet::perlvar{'lonAdmEMail'},$Apache::lonnet::perlvar{'lonSysEMail'}";
! 133: my $subj="LON: $Apache::lonnet::perlvar{'lonHostID'} User ID mismatch";
1.55 matthew 134: system("echo 'User ID mismatch. searchcat.pl must be run as user www.' |\
1.63 ! matthew 135: mail -s '$subj' $emailto > /dev/null");
1.55 matthew 136: exit 1;
137: }
138: #
139: # Let people know we are running
1.63 ! matthew 140: open(LOG,'>>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/logs/searchcat.log');
1.56 matthew 141: &log(0,'==== Searchcat Run '.localtime()."====");
1.57 matthew 142:
143:
1.56 matthew 144: if ($debug) {
145: &log(0,'simulating') if ($simulate);
146: &log(0,'only processing user '.$oneuser) if ($oneuser);
147: &log(0,'verbosity level = '.$verbose);
148: }
1.55 matthew 149: #
150: # Connect to database
151: my $dbh;
1.63 ! matthew 152: if (! ($dbh = DBI->connect("DBI:mysql:loncapa","www",$Apache::lonnet::perlvar{'lonSqlAccess'},
1.55 matthew 153: { RaiseError =>0,PrintError=>0}))) {
1.56 matthew 154: &log(0,"Cannot connect to database!");
1.55 matthew 155: die "MySQL Error: Cannot connect to database!\n";
156: }
157: # This can return an error and still be okay, so we do not bother checking.
158: # (perhaps it should be more robust and check for specific errors)
159: $dbh->do('DROP TABLE IF EXISTS '.$newname);
160: #
161: # Create the new table
162: my $request = &LONCAPA::lonmetadata::create_metadata_storage($newname);
163: $dbh->do($request);
164: if ($dbh->err) {
165: $dbh->disconnect();
1.56 matthew 166: &log(0,"MySQL Error Create: ".$dbh->errstr);
1.55 matthew 167: die $dbh->errstr;
168: }
169: #
170: # find out which users we need to examine
1.63 ! matthew 171: my @domains = sort(&Apache::lonnet::current_machine_domains());
! 172: &log(9,'domains ="'.join('","',@domains).'"');
1.62 matthew 173:
174: foreach my $dom (@domains) {
175: &log(9,'domain = '.$dom);
1.63 ! matthew 176: opendir(RESOURCES,"$Apache::lonnet::perlvar{'lonDocRoot'}/res/$dom");
1.62 matthew 177: my @homeusers =
178: grep {
1.63 ! matthew 179: &ishome("$Apache::lonnet::perlvar{'lonDocRoot'}/res/$dom/$_");
1.62 matthew 180: } grep {
181: !/^\.\.?$/;
182: } readdir(RESOURCES);
183: closedir RESOURCES;
184: &log(5,'users = '.$dom.':'.join(',',@homeusers));
185: #
186: if ($oneuser) {
187: @homeusers=($oneuser);
188: }
189: #
190: # Loop through the users
191: foreach my $user (@homeusers) {
192: &log(0,"=== User: ".$user);
193: &process_dynamic_metadata($user,$dom);
194: #
195: # Use File::Find to get the files we need to read/modify
196: find(
197: {preprocess => \&only_meta_files,
198: #wanted => \&print_filename,
199: #wanted => \&log_metadata,
200: wanted => \&process_meta_file,
1.63 ! matthew 201: }, join('/',($Apache::lonnet::perlvar{'lonDocRoot'},'res',$dom,$user)) );
1.62 matthew 202: }
1.55 matthew 203: }
204: #
205: # Rename the table
1.56 matthew 206: if (! $simulate) {
207: $dbh->do('DROP TABLE IF EXISTS '.$oldname);
208: if (! $dbh->do('RENAME TABLE '.$newname.' TO '.$oldname)) {
209: &log(0,"MySQL Error Rename: ".$dbh->errstr);
210: die $dbh->errstr;
211: } else {
212: &log(1,"MySQL table rename successful.");
213: }
1.55 matthew 214: }
215: if (! $dbh->disconnect) {
1.56 matthew 216: &log(0,"MySQL Error Disconnect: ".$dbh->errstr);
1.55 matthew 217: die $dbh->errstr;
218: }
219: ##
220: ## Finished!
1.56 matthew 221: &log(0,"==== Searchcat completed ".localtime()." ====");
1.55 matthew 222: close(LOG);
1.21 www 223:
1.55 matthew 224: &write_type_count();
225: &write_copyright_count();
1.36 www 226:
1.55 matthew 227: exit 0;
1.28 harris41 228:
1.56 matthew 229: ##
230: ## Status logging routine. Inputs: $level, $message
231: ##
232: ## $level 0 should be used for normal output and error messages
233: ##
234: ## $message does not need to end with \n. In the case of errors
235: ## the message should contain as much information as possible to
236: ## help in diagnosing the problem.
237: ##
238: sub log {
239: my ($level,$message)=@_;
240: $level = 0 if (! defined($level));
241: if ($verbose >= $level) {
242: print LOG $message.$/;
243: }
244: }
245:
1.55 matthew 246: ########################################################
247: ########################################################
248: ### ###
249: ### File::Find support routines ###
250: ### ###
251: ########################################################
252: ########################################################
253: ##
254: ## &only_meta_files
255: ##
256: ## Called by File::Find.
257: ## Takes a list of files/directories in and returns a list of files/directories
258: ## to search.
259: sub only_meta_files {
260: my @PossibleFiles = @_;
261: my @ChosenFiles;
262: foreach my $file (@PossibleFiles) {
263: if ( ($file =~ /\.meta$/ && # Ends in meta
264: $file !~ /\.\d+\.[^\.]+\.meta$/ # is not for a prior version
265: ) || (-d $file )) { # directories are okay
266: # but we do not want /. or /..
267: push(@ChosenFiles,$file);
268: }
1.38 www 269: }
1.55 matthew 270: return @ChosenFiles;
1.38 www 271: }
272:
1.55 matthew 273: ##
274: ##
275: ## Debugging routines, use these for 'wanted' in the File::Find call
276: ##
277: sub print_filename {
278: my ($file) = $_;
279: my $fullfilename = $File::Find::name;
1.56 matthew 280: if ($debug) {
281: if (-d $file) {
282: &log(5," Got directory ".$fullfilename);
283: } else {
284: &log(5," Got file ".$fullfilename);
285: }
1.38 www 286: }
1.55 matthew 287: $_=$file;
1.38 www 288: }
1.28 harris41 289:
1.55 matthew 290: sub log_metadata {
291: my ($file) = $_;
292: my $fullfilename = $File::Find::name;
293: return if (-d $fullfilename); # No need to do anything here for directories
1.56 matthew 294: if ($debug) {
295: &log(6,$fullfilename);
296: my $ref=&metadata($fullfilename);
297: if (! defined($ref)) {
298: &log(6," No data");
299: return;
300: }
301: while (my($key,$value) = each(%$ref)) {
302: &log(6," ".$key." => ".$value);
303: }
304: &count_copyright($ref->{'copyright'});
1.55 matthew 305: }
306: $_=$file;
1.31 harris41 307: }
1.21 www 308:
1.55 matthew 309: ##
310: ## process_meta_file
311: ## Called by File::Find.
312: ## Only input is the filename in $_.
313: sub process_meta_file {
314: my ($file) = $_;
1.56 matthew 315: my $filename = $File::Find::name; # full filename
1.55 matthew 316: return if (-d $filename); # No need to do anything here for directories
317: #
1.56 matthew 318: &log(3,$filename) if ($debug);
1.55 matthew 319: #
320: my $ref=&metadata($filename);
321: #
322: # $url is the original file url, not the metadata file
1.61 matthew 323: my $target = $filename;
324: $target =~ s/\.meta$//;
325: my $url='/res/'.&declutter($target);
1.56 matthew 326: &log(3," ".$url) if ($debug);
1.55 matthew 327: #
328: # Ignore some files based on their metadata
329: if ($ref->{'obsolete'}) {
1.56 matthew 330: &log(3,"obsolete") if ($debug);
1.55 matthew 331: return;
332: }
333: &count_copyright($ref->{'copyright'});
334: if ($ref->{'copyright'} eq 'private') {
1.56 matthew 335: &log(3,"private") if ($debug);
1.55 matthew 336: return;
337: }
338: #
339: # Find the dynamic metadata
340: my %dyn;
341: if ($url=~ m:/default$:) {
342: $url=~ s:/default$:/:;
1.56 matthew 343: &log(3,"Skipping dynamic data") if ($debug);
1.55 matthew 344: } else {
1.56 matthew 345: &log(3,"Retrieving dynamic data") if ($debug);
346: %dyn=&get_dynamic_metadata($url);
1.55 matthew 347: &count_type($url);
348: }
349: #
1.61 matthew 350: if (! defined($ref->{'creationdate'}) ||
351: $ref->{'creationdate'} =~ /^\s*$/) {
352: $ref->{'creationdate'} = (stat($target))[9];
353: }
354: if (! defined($ref->{'lastrevisiondate'}) ||
355: $ref->{'lastrevisiondate'} =~ /^\s*$/) {
356: $ref->{'lastrevisiondate'} = (stat($target))[9];
357: }
1.55 matthew 358: $ref->{'creationdate'} = &sqltime($ref->{'creationdate'});
359: $ref->{'lastrevisiondate'} = &sqltime($ref->{'lastrevisiondate'});
360: my %Data = (
361: %$ref,
362: %dyn,
363: 'url'=>$url,
364: 'version'=>'current');
1.56 matthew 365: if (! $simulate) {
366: my ($count,$err) = &LONCAPA::lonmetadata::store_metadata($dbh,$newname,
367: \%Data);
368: if ($err) {
369: &log(0,"MySQL Error Insert: ".$err);
370: }
371: if ($count < 1) {
372: &log(0,"Unable to insert record into MySQL database for $url");
373: }
1.55 matthew 374: }
375: #
376: # Reset $_ before leaving
377: $_ = $file;
378: }
379:
380: ########################################################
381: ########################################################
382: ### ###
383: ### &metadata($uri) ###
384: ### Retrieve metadata for the given file ###
385: ### ###
386: ########################################################
387: ########################################################
388: sub metadata {
389: my ($uri)=@_;
390: my %metacache=();
391: $uri=&declutter($uri);
392: my $filename=$uri;
393: $uri=~s/\.meta$//;
394: $uri='';
395: if ($filename !~ /\.meta$/) {
396: $filename.='.meta';
397: }
1.63 ! matthew 398: my $metastring=&getfile($Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$filename);
1.55 matthew 399: return undef if (! defined($metastring));
400: my $parser=HTML::TokeParser->new(\$metastring);
401: my $token;
402: while ($token=$parser->get_token) {
403: if ($token->[0] eq 'S') {
404: my $entry=$token->[1];
405: my $unikey=$entry;
406: if (defined($token->[2]->{'part'})) {
407: $unikey.='_'.$token->[2]->{'part'};
408: }
409: if (defined($token->[2]->{'name'})) {
410: $unikey.='_'.$token->[2]->{'name'};
411: }
412: if ($metacache{$uri.'keys'}) {
413: $metacache{$uri.'keys'}.=','.$unikey;
414: } else {
415: $metacache{$uri.'keys'}=$unikey;
416: }
417: foreach ( @{$token->[3]}) {
418: $metacache{$uri.''.$unikey.'.'.$_}=$token->[2]->{$_};
419: }
420: if (! ($metacache{$uri.''.$unikey}=$parser->get_text('/'.$entry))){
421: $metacache{$uri.''.$unikey} =
422: $metacache{$uri.''.$unikey.'.default'};
423: }
424: } # End of ($token->[0] eq 'S')
425: }
426: return \%metacache;
1.31 harris41 427: }
1.28 harris41 428:
1.55 matthew 429: ##
430: ## &getfile($filename)
431: ## Slurps up an entire file into a scalar.
432: ## Returns undef if the file does not exist
433: sub getfile {
434: my $file = shift();
435: if (! -e $file ) {
436: return undef;
437: }
438: my $fh=IO::File->new($file);
439: my $contents = '';
440: while (<$fh>) {
441: $contents .= $_;
442: }
443: return $contents;
444: }
1.28 harris41 445:
1.55 matthew 446: ########################################################
447: ########################################################
448: ### ###
449: ### Dynamic Metadata ###
450: ### ###
451: ########################################################
452: ########################################################
1.56 matthew 453: ##
1.58 www 454: ## Dynamic metadata description (incomplete)
455: ##
456: ## For a full description of all fields,
457: ## see LONCAPA::lonmetadata
1.56 matthew 458: ##
459: ## Field Type
460: ##-----------------------------------------------------------
461: ## count integer
462: ## course integer
1.58 www 463: ## course_list comma separated list of course ids
1.56 matthew 464: ## avetries real
1.58 www 465: ## avetries_list comma separated list of real numbers
1.56 matthew 466: ## stdno real
1.58 www 467: ## stdno_list comma separated list of real numbers
1.56 matthew 468: ## usage integer
1.58 www 469: ## usage_list comma separated list of resources
1.56 matthew 470: ## goto scalar
1.58 www 471: ## goto_list comma separated list of resources
1.56 matthew 472: ## comefrom scalar
1.58 www 473: ## comefrom_list comma separated list of resources
1.56 matthew 474: ## difficulty real
1.58 www 475: ## difficulty_list comma separated list of real numbers
1.56 matthew 476: ## sequsage scalar
1.58 www 477: ## sequsage_list comma separated list of resources
1.56 matthew 478: ## clear real
479: ## technical real
480: ## correct real
481: ## helpful real
482: ## depth real
483: ## comments html of all the comments made
484: ##
485: {
486:
487: my %DynamicData;
488: my %Counts;
489:
490: sub process_dynamic_metadata {
491: my ($user,$dom) = @_;
492: undef(%DynamicData);
493: undef(%Counts);
494: #
495: my $prodir = &propath($dom,$user);
1.55 matthew 496: #
1.56 matthew 497: # Read in the dynamic metadata
1.55 matthew 498: my %evaldata;
499: if (! tie(%evaldata,'GDBM_File',
500: $prodir.'/nohist_resevaldata.db',&GDBM_READER(),0640)) {
1.56 matthew 501: return 0;
1.55 matthew 502: }
1.56 matthew 503: #
1.57 matthew 504: %DynamicData = &LONCAPA::lonmetadata::process_reseval_data(\%evaldata);
1.55 matthew 505: untie(%evaldata);
1.62 matthew 506: $DynamicData{'domain'} = $dom;
507: print('user = '.$user.' domain = '.$dom.$/);
1.56 matthew 508: #
509: # Read in the access count data
510: &log(7,'Reading access count data') if ($debug);
511: my %countdata;
512: if (! tie(%countdata,'GDBM_File',
513: $prodir.'/nohist_accesscount.db',&GDBM_READER(),0640)) {
514: return 0;
515: }
516: while (my ($key,$count) = each(%countdata)) {
517: next if ($key !~ /^$dom/);
518: $key = &unescape($key);
519: &log(8,' Count '.$key.' = '.$count) if ($debug);
520: $Counts{$key}=$count;
521: }
522: untie(%countdata);
523: if ($debug) {
524: &log(7,scalar(keys(%Counts)).
525: " Counts read for ".$user."@".$dom);
526: &log(7,scalar(keys(%DynamicData)).
527: " Dynamic metadata read for ".$user."@".$dom);
528: }
529: #
530: return 1;
531: }
532:
533: sub get_dynamic_metadata {
534: my ($url) = @_;
535: $url =~ s:^/res/::;
1.57 matthew 536: my %data = &LONCAPA::lonmetadata::process_dynamic_metadata($url,
537: \%DynamicData);
1.56 matthew 538: # find the count
539: $data{'count'} = $Counts{$url};
540: #
541: # Log the dynamic metadata
542: if ($debug) {
543: while (my($k,$v)=each(%data)) {
544: &log(8," ".$k." => ".$v);
545: }
1.44 www 546: }
1.56 matthew 547: return %data;
1.30 www 548: }
1.28 harris41 549:
1.56 matthew 550: } # End of %DynamicData and %Counts scope
551:
1.55 matthew 552: ########################################################
553: ########################################################
554: ### ###
555: ### Counts ###
556: ### ###
557: ########################################################
558: ########################################################
559: {
1.1 harris41 560:
1.55 matthew 561: my %countext;
1.15 harris41 562:
1.55 matthew 563: sub count_type {
564: my $file=shift;
565: $file=~/\.(\w+)$/;
566: my $ext=lc($1);
567: $countext{$ext}++;
1.31 harris41 568: }
1.1 harris41 569:
1.55 matthew 570: sub write_type_count {
571: open(RESCOUNT,'>/home/httpd/html/lon-status/rescount.txt');
572: while (my ($extension,$count) = each(%countext)) {
573: print RESCOUNT $extension.'='.$count.'&';
1.47 www 574: }
1.55 matthew 575: print RESCOUNT 'time='.time."\n";
576: close(RESCOUNT);
1.31 harris41 577: }
1.27 www 578:
1.55 matthew 579: } # end of scope for %countext
1.34 matthew 580:
1.55 matthew 581: {
1.34 matthew 582:
1.55 matthew 583: my %copyrights;
1.44 www 584:
1.55 matthew 585: sub count_copyright {
586: $copyrights{@_[0]}++;
1.31 harris41 587: }
1.33 matthew 588:
1.55 matthew 589: sub write_copyright_count {
590: open(COPYCOUNT,'>/home/httpd/html/lon-status/copyrightcount.txt');
591: while (my ($copyright,$count) = each(%copyrights)) {
592: print COPYCOUNT $copyright.'='.$count.'&';
1.31 harris41 593: }
1.55 matthew 594: print COPYCOUNT 'time='.time."\n";
595: close(COPYCOUNT);
1.31 harris41 596: }
1.28 harris41 597:
1.55 matthew 598: } # end of scope for %copyrights
1.28 harris41 599:
1.55 matthew 600: ########################################################
601: ########################################################
602: ### ###
603: ### Miscellanous Utility Routines ###
604: ### ###
605: ########################################################
606: ########################################################
607: ##
608: ## &ishome($username)
609: ## Returns 1 if $username is a LON-CAPA author, 0 otherwise
610: ## (copied from lond, modification of the return value)
1.31 harris41 611: sub ishome {
612: my $author=shift;
613: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
614: my ($udom,$uname)=split(/\//,$author);
615: my $proname=propath($udom,$uname);
616: if (-e $proname) {
617: return 1;
618: } else {
619: return 0;
620: }
621: }
1.28 harris41 622:
1.55 matthew 623: ##
624: ## &propath($udom,$uname)
625: ## Returns the path to the users LON-CAPA directory
626: ## (copied from lond)
1.31 harris41 627: sub propath {
628: my ($udom,$uname)=@_;
629: $udom=~s/\W//g;
630: $uname=~s/\W//g;
631: my $subdir=$uname.'__';
632: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
1.63 ! matthew 633: my $proname="$Apache::lonnet::perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
1.31 harris41 634: return $proname;
635: }
1.28 harris41 636:
1.55 matthew 637: ##
638: ## &sqltime($timestamp)
639: ##
640: ## Convert perl $timestamp to MySQL time. MySQL expects YYYY-MM-DD HH:MM:SS
641: ##
1.31 harris41 642: sub sqltime {
1.55 matthew 643: my ($time) = @_;
644: my $mysqltime;
645: if ($time =~
646: /(\d+)-(\d+)-(\d+) # YYYY-MM-DD
647: \s # a space
648: (\d+):(\d+):(\d+) # HH:MM::SS
649: /x ) {
650: # Some of the .meta files have the time in mysql
651: # format already, so just make sure they are 0 padded and
652: # pass them back.
653: $mysqltime = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
654: $1,$2,$3,$4,$5,$6);
655: } elsif ($time =~ /^\d+$/) {
656: my @TimeData = gmtime($time);
657: # Alter the month to be 1-12 instead of 0-11
658: $TimeData[4]++;
659: # Alter the year to be from 0 instead of from 1900
660: $TimeData[5]+=1900;
661: $mysqltime = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
662: @TimeData[5,4,3,2,1,0]);
1.56 matthew 663: } elsif (! defined($time) || $time == 0) {
664: $mysqltime = 0;
1.55 matthew 665: } else {
1.56 matthew 666: &log(0," sqltime:Unable to decode time ".$time);
1.55 matthew 667: $mysqltime = 0;
668: }
669: return $mysqltime;
1.31 harris41 670: }
1.28 harris41 671:
1.55 matthew 672: ##
673: ## &declutter($filename)
674: ## Given a filename, returns a url for the filename.
675: sub declutter {
676: my $thisfn=shift;
1.63 ! matthew 677: $thisfn=~s/^$Apache::lonnet::perlvar{'lonDocRoot'}//;
1.55 matthew 678: $thisfn=~s/^\///;
679: $thisfn=~s/^res\///;
680: return $thisfn;
1.31 harris41 681: }
1.28 harris41 682:
1.55 matthew 683: ##
684: ## Escape / Unescape special characters
685: sub unescape {
686: my $str=shift;
687: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
688: return $str;
1.31 harris41 689: }
1.28 harris41 690:
1.55 matthew 691: sub escape {
692: my $str=shift;
693: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
694: return $str;
1.45 www 695: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>