Annotation of loncom/metadata_database/LONCAPA/lonmetadata.pm, revision 1.38
1.1 matthew 1: # The LearningOnline Network with CAPA
2: #
1.38 ! www 3: # $Id: lonmetadata.pm,v 1.37 2012/03/15 21:54:58 www Exp $
1.1 matthew 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: package LONCAPA::lonmetadata;
30:
31: use strict;
32: use DBI;
1.16 raeburn 33: use HTML::TokeParser;
1.14 raeburn 34: use vars qw($Metadata_Table_Description $Portfolio_metadata_table_description
1.23 raeburn 35: $Portfolio_access_table_description $Fulltext_indicies $Portfolio_metadata_indices $Portfolio_access_indices $Portfolio_addedfields_table_description $Portfolio_addedfields_indices $Allusers_table_description $Allusers_indices);
1.1 matthew 36:
37: ######################################################################
38: ######################################################################
39:
40: =pod
41:
42: =head1 Name
43:
44: lonmetadata
45:
46: =head1 Synopsis
47:
48: lonmetadata holds a description of the metadata table and provides
49: wrappers for the storage and retrieval of metadata to/from the database.
50:
51: =head1 Description
52:
53: =head1 Methods
54:
55: =over 4
56:
57: =cut
58:
59: ######################################################################
60: ######################################################################
61:
62: =pod
63:
64: =item Old table creation command
65:
66: CREATE TABLE IF NOT EXISTS metadata
67: (title TEXT,
68: author TEXT,
69: subject TEXT,
70: url TEXT,
71: keywords TEXT,
72: version TEXT,
73: notes TEXT,
74: abstract TEXT,
75: mime TEXT,
76: language TEXT,
77: creationdate DATETIME,
78: lastrevisiondate DATETIME,
79: owner TEXT,
80: copyright TEXT,
1.12 matthew 81: domain TEXT
1.1 matthew 82:
83: FULLTEXT idx_title (title),
84: FULLTEXT idx_author (author),
85: FULLTEXT idx_subject (subject),
86: FULLTEXT idx_url (url),
87: FULLTEXT idx_keywords (keywords),
88: FULLTEXT idx_version (version),
89: FULLTEXT idx_notes (notes),
90: FULLTEXT idx_abstract (abstract),
91: FULLTEXT idx_mime (mime),
92: FULLTEXT idx_language (language),
93: FULLTEXT idx_owner (owner),
94: FULLTEXT idx_copyright (copyright))
95:
1.32 raeburn 96: ENGINE=MYISAM;
1.1 matthew 97:
98: =cut
99:
100: ######################################################################
101: ######################################################################
1.14 raeburn 102: $Metadata_Table_Description =
103: [
1.1 matthew 104: { name => 'title', type=>'TEXT'},
105: { name => 'author', type=>'TEXT'},
106: { name => 'subject', type=>'TEXT'},
107: { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' },
108: { name => 'keywords', type=>'TEXT'},
109: { name => 'version', type=>'TEXT'},
110: { name => 'notes', type=>'TEXT'},
111: { name => 'abstract', type=>'TEXT'},
112: { name => 'mime', type=>'TEXT'},
113: { name => 'language', type=>'TEXT'},
114: { name => 'creationdate', type=>'DATETIME'},
115: { name => 'lastrevisiondate', type=>'DATETIME'},
116: { name => 'owner', type=>'TEXT'},
117: { name => 'copyright', type=>'TEXT'},
1.12 matthew 118: { name => 'domain', type=>'TEXT'},
1.1 matthew 119: #--------------------------------------------------
120: { name => 'dependencies', type=>'TEXT'},
121: { name => 'modifyinguser', type=>'TEXT'},
122: { name => 'authorspace', type=>'TEXT'},
123: { name => 'lowestgradelevel', type=>'INT'},
124: { name => 'highestgradelevel', type=>'INT'},
125: { name => 'standards', type=>'TEXT'},
126: { name => 'count', type=>'INT'},
127: { name => 'course', type=>'INT'},
128: { name => 'course_list', type=>'TEXT'},
129: { name => 'goto', type=>'INT'},
130: { name => 'goto_list', type=>'TEXT'},
131: { name => 'comefrom', type=>'INT'},
132: { name => 'comefrom_list', type=>'TEXT'},
133: { name => 'sequsage', type=>'INT'},
134: { name => 'sequsage_list', type=>'TEXT'},
135: { name => 'stdno', type=>'INT'},
136: { name => 'stdno_list', type=>'TEXT'},
137: { name => 'avetries', type=>'FLOAT'},
138: { name => 'avetries_list', type=>'TEXT'},
139: { name => 'difficulty', type=>'FLOAT'},
140: { name => 'difficulty_list',type=>'TEXT'},
1.9 matthew 141: { name => 'disc', type=>'FLOAT'},
142: { name => 'disc_list', type=>'TEXT'},
1.1 matthew 143: { name => 'clear', type=>'FLOAT'},
144: { name => 'technical', type=>'FLOAT'},
145: { name => 'correct', type=>'FLOAT'},
146: { name => 'helpful', type=>'FLOAT'},
147: { name => 'depth', type=>'FLOAT'},
148: { name => 'hostname', type=> 'TEXT'},
149: #--------------------------------------------------
1.14 raeburn 150: ];
1.1 matthew 151:
1.14 raeburn 152: $Fulltext_indicies = [ qw/
1.1 matthew 153: title
154: author
155: subject
156: url
157: keywords
158: version
159: notes
160: abstract
161: mime
162: language
163: owner
1.14 raeburn 164: copyright/ ];
165:
166: ######################################################################
167: ######################################################################
168: $Portfolio_metadata_table_description =
169: [
170: { name => 'title', type=>'TEXT'},
171: { name => 'author', type=>'TEXT'},
172: { name => 'subject', type=>'TEXT'},
173: { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' },
174: { name => 'keywords', type=>'TEXT'},
175: { name => 'version', type=>'TEXT'},
176: { name => 'notes', type=>'TEXT'},
177: { name => 'abstract', type=>'TEXT'},
178: { name => 'mime', type=>'TEXT'},
179: { name => 'language', type=>'TEXT'},
180: { name => 'creationdate', type=>'DATETIME'},
181: { name => 'lastrevisiondate', type=>'DATETIME'},
182: { name => 'owner', type=>'TEXT'},
183: { name => 'copyright', type=>'TEXT'},
184: { name => 'domain', type=>'TEXT'},
185: { name => 'groupname', type=>'TEXT'},
186: { name => 'courserestricted', type=>'TEXT'},
187: #--------------------------------------------------
188: { name => 'dependencies', type=>'TEXT'},
189: { name => 'modifyinguser', type=>'TEXT'},
190: { name => 'authorspace', type=>'TEXT'},
191: { name => 'lowestgradelevel', type=>'INT'},
192: { name => 'highestgradelevel', type=>'INT'},
193: { name => 'standards', type=>'TEXT'},
194: { name => 'hostname', type=> 'TEXT'},
195: #--------------------------------------------------
196: ];
197:
198: $Portfolio_metadata_indices = [qw/
199: title
200: author
201: subject
202: url
203: keywords
204: version
205: notes
206: abstract
207: mime
208: language
209: owner/];
210:
211: ######################################################################
212: ######################################################################
213:
214: $Portfolio_access_table_description =
215: [
216: { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' },
217: { name => 'keynum', type=>'TEXT', restrictions => 'NOT NULL' },
218: { name => 'scope', type=>'TEXT'},
219: { name => 'start', type=>'DATETIME'},
220: { name => 'end', type=>'DATETIME'},
221: ];
222:
223: $Portfolio_access_indices = [qw/
224: url
225: keynum
226: scope
227: start
228: end/];
1.1 matthew 229:
230: ######################################################################
231: ######################################################################
232:
1.14 raeburn 233: $Portfolio_addedfields_table_description =
234: [
235: { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' },
236: { name => 'field', type=>'TEXT', restrictions => 'NOT NULL' },
237: { name => 'courserestricted', type=>'TEXT', restrictions => 'NOT NULL' },
238: { name => 'value', type=>'TEXT'},
239: ];
240:
241: $Portfolio_addedfields_indices = [qw/
242: url
243: field
244: value
245: courserestricted/];
246:
247: ######################################################################
248: ######################################################################
249:
1.23 raeburn 250: $Allusers_table_description =
251: [
252: { name => 'username', type=>'TEXT', restrictions => 'NOT NULL' },
253: { name => 'domain', type=>'TEXT', restrictions => 'NOT NULL' },
254: { name => 'lastname', type=>'TEXT',},
255: { name => 'firstname', type=>'TEXT'},
256: { name => 'middlename', type=>'TEXT'},
257: { name => 'generation', type=>'TEXT'},
258: { name => 'permanentemail', type=>'TEXT'},
259: { name => 'id', type=>'TEXT'},
260: ];
261:
262: $Allusers_indices = [qw/
263: username
264: domain
265: lastname
266: firstname/];
267:
268: ######################################################################
269: ######################################################################
1.14 raeburn 270:
1.1 matthew 271: =pod
272:
273: =item &describe_metadata_storage
274:
275: Input: None
276:
1.2 matthew 277: Returns: An array of hash references describing the columns and indicies
278: of the metadata table(s).
1.1 matthew 279:
280: =cut
281:
282: ######################################################################
283: ######################################################################
1.14 raeburn 284: sub describe_metadata_storage {
285: my ($tabletype) = @_;
286: my %table_description = (
287: metadata => $Metadata_Table_Description,
288: portfolio_metadata => $Portfolio_metadata_table_description,
289: portfolio_access => $Portfolio_access_table_description,
290: portfolio_addedfields => $Portfolio_addedfields_table_description,
1.23 raeburn 291: allusers => $Allusers_table_description,
1.14 raeburn 292: );
293: my %index_description = (
294: metadata => $Fulltext_indicies,
295: portfolio_metadata => $Portfolio_metadata_indices,
296: portfolio_access => $Portfolio_access_indices,
297: portfolio_addedfields => $Portfolio_addedfields_indices,
1.23 raeburn 298: allusers => $Allusers_indices,
1.14 raeburn 299: );
300: if ($tabletype eq 'portfolio_search') {
301: my @portfolio_search_table = @{$table_description{portfolio_metadata}};
302: foreach my $item (@{$table_description{portfolio_access}}) {
303: if (ref($item) eq 'HASH') {
304: if ($item->{'name'} eq 'url') {
305: next;
306: }
307: }
308: push(@portfolio_search_table,$item);
309: }
310: my @portfolio_search_indices = @{$index_description{portfolio_metadata}};
311: push(@portfolio_search_indices,('scope','keynum'));
312: return (\@portfolio_search_table,\@portfolio_search_indices);
313: } else {
314: return ($table_description{$tabletype},$index_description{$tabletype});
315: }
1.1 matthew 316: }
317:
318: ######################################################################
319: ######################################################################
320:
321: =pod
322:
323: =item create_metadata_storage()
324:
1.3 matthew 325: Inputs: table name (optional): the name of the table. Default is 'metadata'.
1.1 matthew 326:
327: Returns: A perl string which, when executed by MySQL, will cause the
328: metadata storage to be initialized.
329:
330: =cut
331:
332: ######################################################################
333: ######################################################################
334: sub create_metadata_storage {
1.14 raeburn 335: my ($tablename,$tabletype) = @_;
1.3 matthew 336: $tablename = 'metadata' if (! defined($tablename));
1.14 raeburn 337: $tabletype = 'metadata' if (! defined($tabletype));
1.1 matthew 338: my $request = "CREATE TABLE IF NOT EXISTS ".$tablename." ";
339: #
340: # Process the columns (this code is stolen from lonmysql.pm)
341: my @Columns;
342: my $col_des; # mysql column description
1.14 raeburn 343: my ($table_columns,$table_indices) =
344: &describe_metadata_storage($tabletype);
345: my %coltype;
346: foreach my $coldata (@{$table_columns}) {
1.1 matthew 347: my $column = $coldata->{'name'};
1.14 raeburn 348: $coltype{$column} = $coldata->{'type'};
1.1 matthew 349: $col_des = '';
350: if (lc($coldata->{'type'}) =~ /(enum|set)/) { # 'enum' or 'set'
351: $col_des.=$column." ".$coldata->{'type'}."('".
352: join("', '",@{$coldata->{'values'}})."')";
353: } else {
354: $col_des.=$column." ".$coldata->{'type'};
355: if (exists($coldata->{'size'})) {
356: $col_des.="(".$coldata->{'size'}.")";
357: }
358: }
1.30 raeburn 359: if (($tablename =~ /allusers/) && ($column eq 'username')) {
1.31 raeburn 360: $col_des .= ' CHARACTER SET latin1 COLLATE latin1_general_cs';
1.30 raeburn 361: }
1.1 matthew 362: # Modifiers
363: if (exists($coldata->{'restrictions'})){
364: $col_des.=" ".$coldata->{'restrictions'};
365: }
366: if (exists($coldata->{'default'})) {
367: $col_des.=" DEFAULT '".$coldata->{'default'}."'";
368: }
369: $col_des.=' AUTO_INCREMENT' if (exists($coldata->{'auto_inc'}) &&
370: ($coldata->{'auto_inc'} eq 'yes'));
371: $col_des.=' PRIMARY KEY' if (exists($coldata->{'primary_key'}) &&
372: ($coldata->{'primary_key'} eq 'yes'));
373: } continue {
374: # skip blank items.
375: push (@Columns,$col_des) if ($col_des ne '');
376: }
1.14 raeburn 377: foreach my $colname (@{$table_indices}) {
378: my $text;
379: if ($coltype{$colname} eq 'TEXT') {
380: $text = 'FULLTEXT ';
381: } else {
382: $text = 'INDEX ';
383: }
384: $text .= 'idx_'.$colname.' ('.$colname.')';
1.1 matthew 385: push (@Columns,$text);
386: }
1.33 raeburn 387: $request .= "(".join(", ",@Columns).") ENGINE=MyISAM";
1.1 matthew 388: return $request;
389: }
390:
391: ######################################################################
392: ######################################################################
393:
394: =pod
395:
396: =item store_metadata()
397:
1.14 raeburn 398: Inputs: database handle ($dbh), a table name, table type and a hash or hash
399: reference containing the metadata for a single resource.
1.1 matthew 400:
401: Returns: 1 on success, 0 on failure to store.
402:
403: =cut
404:
405: ######################################################################
406: ######################################################################
1.2 matthew 407: {
408: ##
409: ## WARNING: The following cleverness may cause trouble in cases where
410: ## the dbi connection is dropped and recreated - a stale statement
411: ## handler may linger around and cause trouble.
412: ##
413: ## In most scripts, this will work fine. If the dbi is going to be
414: ## dropped and (possibly) later recreated, call &clear_sth. Yes it
1.14 raeburn 415: ## is annoying but $sth apparently does not have a link back to the
1.2 matthew 416: ## $dbh, so we can't check our validity.
417: ##
418: my $sth = undef;
1.4 matthew 419: my $sth_table = undef;
1.2 matthew 420:
421: sub create_statement_handler {
1.14 raeburn 422: my ($dbh,$tablename,$tabletype) = @_;
1.4 matthew 423: $tablename = 'metadata' if (! defined($tablename));
1.14 raeburn 424: $tabletype = 'metadata' if (! defined($tabletype));
425: my ($table_columns,$table_indices) =
426: &describe_metadata_storage($tabletype);
1.4 matthew 427: $sth_table = $tablename;
428: my $request = 'INSERT INTO '.$tablename.' VALUES(';
1.14 raeburn 429: foreach (@{$table_columns}) {
1.2 matthew 430: $request .= '?,';
431: }
432: chop $request;
433: $request.= ')';
434: $sth = $dbh->prepare($request);
435: return;
436: }
437:
1.4 matthew 438: sub clear_sth { $sth=undef; $sth_table=undef;}
1.2 matthew 439:
1.1 matthew 440: sub store_metadata {
1.14 raeburn 441: my ($dbh,$tablename,$tabletype,@Metadata)=@_;
1.2 matthew 442: my $errors = '';
1.4 matthew 443: if (! defined($sth) ||
444: ( defined($tablename) && ($sth_table ne $tablename)) ||
445: (! defined($tablename) && $sth_table ne 'metadata')) {
1.14 raeburn 446: &create_statement_handler($dbh,$tablename,$tabletype);
1.2 matthew 447: }
448: my $successcount = 0;
1.14 raeburn 449: if (! defined($tabletype)) {
450: $tabletype = 'metadata';
451: }
452: my ($table_columns,$table_indices) =
453: &describe_metadata_storage($tabletype);
1.10 matthew 454: foreach my $mdata (@Metadata) {
1.2 matthew 455: next if (ref($mdata) ne "HASH");
456: my @MData;
1.14 raeburn 457: foreach my $field (@{$table_columns}) {
1.10 matthew 458: my $fname = $field->{'name'};
459: if (exists($mdata->{$fname}) &&
460: defined($mdata->{$fname}) &&
461: $mdata->{$fname} ne '') {
462: if ($mdata->{$fname} eq 'nan' ||
463: $mdata->{$fname} eq '') {
1.5 matthew 464: push(@MData,'NULL');
465: } else {
1.34 droeschl 466: push(@MData, $field->{type} eq 'DATETIME' ?
467: sqltime($mdata->{$fname}) : $mdata->{$fname});
1.5 matthew 468: }
1.2 matthew 469: } else {
470: push(@MData,undef);
471: }
472: }
473: $sth->execute(@MData);
474: if (! $sth->err) {
475: $successcount++;
476: } else {
477: $errors = join(',',$errors,$sth->errstr);
478: }
1.10 matthew 479: $errors =~ s/^,//;
1.2 matthew 480: }
481: if (wantarray()) {
482: return ($successcount,$errors);
483: } else {
484: return $successcount;
485: }
486: }
1.1 matthew 487:
488: }
489:
490: ######################################################################
491: ######################################################################
492:
493: =pod
494:
1.24 albertel 495: =item lookup_metadata()
1.1 matthew 496:
497: Inputs: database handle ($dbh) and a hash or hash reference containing
498: metadata which will be used for a search.
499:
1.2 matthew 500: Returns: scalar with error string on failure, array reference on success.
501: The array reference is the same one returned by $sth->fetchall_arrayref().
1.1 matthew 502:
503: =cut
504:
505: ######################################################################
506: ######################################################################
1.2 matthew 507: sub lookup_metadata {
1.10 matthew 508: my ($dbh,$condition,$fetchparameter,$tablename) = @_;
509: $tablename = 'metadata' if (! defined($tablename));
1.2 matthew 510: my $error;
511: my $returnvalue=[];
1.10 matthew 512: my $request = 'SELECT * FROM '.$tablename;
1.2 matthew 513: if (defined($condition)) {
514: $request .= ' WHERE '.$condition;
515: }
516: my $sth = $dbh->prepare($request);
517: if ($sth->err) {
518: $error = $sth->errstr;
519: }
520: if (! $error) {
521: $sth->execute();
522: if ($sth->err) {
523: $error = $sth->errstr;
524: } else {
525: $returnvalue = $sth->fetchall_arrayref($fetchparameter);
526: if ($sth->err) {
527: $error = $sth->errstr;
528: }
529: }
1.16 raeburn 530: }
1.2 matthew 531: return ($error,$returnvalue);
532: }
1.1 matthew 533:
534: ######################################################################
535: ######################################################################
536:
537: =pod
538:
539: =item delete_metadata()
540:
1.10 matthew 541: Removes a single metadata record, based on its url.
542:
543: Inputs: $dbh, the database handler.
544: $tablename, the name of the metadata table to remove from. default: 'metadata'
1.23 raeburn 545: $delitem, the resource to remove from the metadata database, in the form:
546: url = quoted url
1.10 matthew 547:
548: Returns: undef on success, dbh errorstr on failure.
549:
550: =cut
551:
552: ######################################################################
553: ######################################################################
554: sub delete_metadata {
1.23 raeburn 555: my ($dbh,$tablename,$delitem) = @_;
1.10 matthew 556: $tablename = 'metadata' if (! defined($tablename));
1.23 raeburn 557: my ($error,$delete_command);
558: if ($delitem eq '') {
559: $error = 'deletion aborted - no resource specified';
560: } else {
561: $delete_command = 'DELETE FROM '.$tablename.' WHERE '.$delitem;
562: $dbh->do($delete_command);
563: if ($dbh->err) {
564: $error = $dbh->errstr();
565: }
1.10 matthew 566: }
567: return $error;
568: }
569:
570: ######################################################################
571: ######################################################################
572:
573: =pod
574:
575: =item update_metadata
576:
577: Updates metadata record in mysql database. It does not matter if the record
578: currently exists. Fields not present in the new metadata will be taken
579: from the current record, if it exists. To delete an entry for a key, set
580: it to "" or undef.
581:
582: Inputs:
583: $dbh, database handle
584: $newmetadata, hash reference containing the new metadata
585: $tablename, metadata table name. Defaults to 'metadata'.
1.23 raeburn 586: $tabletype, type of table (metadata, portfolio_metadata, portfolio_access,
587: allusers)
588: $conditions, optional hash of conditions to use in SQL queries;
589: default used if none provided.
1.10 matthew 590:
591: Returns:
592: $error on failure. undef on success.
1.1 matthew 593:
594: =cut
595:
596: ######################################################################
597: ######################################################################
1.10 matthew 598: sub update_metadata {
1.23 raeburn 599: my ($dbh,$tablename,$tabletype,$newmetadata,$conditions)=@_;
600: my ($error,$condition);
1.10 matthew 601: $tablename = 'metadata' if (! defined($tablename));
1.14 raeburn 602: $tabletype = 'metadata' if (! defined($tabletype));
1.23 raeburn 603: if (ref($conditions) eq 'HASH') {
604: my @items;
605: foreach my $key (keys(%{$conditions})) {
606: if (! exists($newmetadata->{$key})) {
607: $error .= "Unable to update: no $key specified";
608: } else {
609: push(@items,"$key = ".$dbh->quote($newmetadata->{$key}));
610: }
611: }
612: $condition = join(' AND ',@items);
613: } else {
614: if (! exists($newmetadata->{'url'})) {
615: $error = 'Unable to update: no url specified';
616: } else {
617: $condition = 'url = '.$dbh->quote($newmetadata->{'url'});
618: }
1.10 matthew 619: }
620: return $error if (defined($error));
621: #
622: # Retrieve current values
623: my $row;
1.23 raeburn 624: ($error,$row) = &lookup_metadata($dbh,$condition,undef,$tablename);
1.10 matthew 625: return $error if ($error);
1.14 raeburn 626: my %metadata = &LONCAPA::lonmetadata::metadata_col_to_hash($tabletype,@{$row->[0]});
1.10 matthew 627: #
628: # Update metadata values
629: while (my ($key,$value) = each(%$newmetadata)) {
630: $metadata{$key} = $value;
631: }
632: #
633: # Delete old data (deleting a nonexistant record does not produce an error.
1.23 raeburn 634: $error = &delete_metadata($dbh,$tablename,$condition);
1.10 matthew 635: return $error if (defined($error));
636: #
637: # Store updated metadata
638: my $success;
1.14 raeburn 639: ($success,$error) = &store_metadata($dbh,$tablename,$tabletype,\%metadata);
1.10 matthew 640: return $error;
641: }
1.1 matthew 642:
643: ######################################################################
644: ######################################################################
1.5 matthew 645:
1.6 matthew 646: =pod
647:
648: =item metdata_col_to_hash
649:
650: Input: Array of metadata columns
651:
652: Return: Hash with the metadata columns as keys and the array elements
653: passed in as values
654:
655: =cut
656:
657: ######################################################################
658: ######################################################################
659: sub metadata_col_to_hash {
1.14 raeburn 660: my ($tabletype,@cols)=@_;
1.6 matthew 661: my %hash=();
1.14 raeburn 662: my ($columns,$indices) = &describe_metadata_storage($tabletype);
663: for (my $i=0; $i<@{$columns};$i++) {
664: $hash{$columns->[$i]->{'name'}}=$cols[$i];
665: unless ($hash{$columns->[$i]->{'name'}}) {
666: if ($columns->[$i]->{'type'} eq 'TEXT') {
667: $hash{$columns->[$i]->{'name'}}='';
668: } elsif ($columns->[$i]->{'type'} eq 'DATETIME') {
669: $hash{$columns->[$i]->{'name'}}='0000-00-00 00:00:00';
1.13 www 670: } else {
1.14 raeburn 671: $hash{$columns->[$i]->{'name'}}=0;
1.13 www 672: }
673: }
1.6 matthew 674: }
675: return %hash;
676: }
1.5 matthew 677:
678: ######################################################################
679: ######################################################################
680:
681: =pod
682:
1.8 matthew 683: =item nohist_resevaldata.db data structure
684:
685: The nohist_resevaldata.db file has the following possible keys:
686:
687: Statistics Data (values are integers, perl times, or real numbers)
688: ------------------------------------------
689: $course___$resource___avetries
690: $course___$resource___count
691: $course___$resource___difficulty
692: $course___$resource___stdno
693: $course___$resource___timestamp
694:
695: Evaluation Data (values are on a 1 to 5 scale)
696: ------------------------------------------
697: $username@$dom___$resource___clear
698: $username@$dom___$resource___comments
699: $username@$dom___$resource___depth
700: $username@$dom___$resource___technical
701: $username@$dom___$resource___helpful
1.11 www 702: $username@$dom___$resource___correct
1.8 matthew 703:
704: Course Context Data
705: ------------------------------------------
706: $course___$resource___course course id
707: $course___$resource___comefrom resource preceeding this resource
708: $course___$resource___goto resource following this resource
709: $course___$resource___usage resource containing this resource
710:
711: New statistical data storage
712: ------------------------------------------
713: $course&$sec&$numstud___$resource___stats
714: $sec is a string describing the sections: all, 1 2, 1 2 3,...
715: Value is a '&' deliminated list of key=value pairs.
716: Possible keys are (currently) disc,course,sections,difficulty,
717: stdno, timestamp
718:
719: =cut
720:
721: ######################################################################
722: ######################################################################
723:
724: =pod
725:
1.5 matthew 726: =item &process_reseval_data
727:
728: Process a nohist_resevaldata hash into a more complex data structure.
729:
730: Input: Hash reference containing reseval data
731:
732: Returns: Hash with the following structure:
733:
734: $hash{$url}->{'statistics'}->{$courseid}->{'avetries'} = $value
735: $hash{$url}->{'statistics'}->{$courseid}->{'count'} = $value
736: $hash{$url}->{'statistics'}->{$courseid}->{'difficulty'} = $value
737: $hash{$url}->{'statistics'}->{$courseid}->{'stdno'} = $value
738: $hash{$url}->{'statistics'}->{$courseid}->{'timestamp'} = $value
739:
740: $hash{$url}->{'evaluation'}->{$username}->{'clear'} = $value
741: $hash{$url}->{'evaluation'}->{$username}->{'comments'} = $value
742: $hash{$url}->{'evaluation'}->{$username}->{'depth'} = $value
743: $hash{$url}->{'evaluation'}->{$username}->{'technical'} = $value
744: $hash{$url}->{'evaluation'}->{$username}->{'helpful'} = $value
745:
746: $hash{$url}->{'course'} = \@Courses
747: $hash{$url}->{'comefrom'} = \@Resources
748: $hash{$url}->{'goto'} = \@Resources
749: $hash{$url}->{'usage'} = \@Resources
750:
751: $hash{$url}->{'stats'}->{$courseid\_$section}->{$key} = $value
752:
753: =cut
754:
755: ######################################################################
756: ######################################################################
757: sub process_reseval_data {
758: my ($evaldata) = @_;
759: my %DynamicData;
760: #
761: # Process every stored element
762: while (my ($storedkey,$value) = each(%{$evaldata})) {
1.37 www 763: my (@keycomponents) = split('___',$storedkey);
764: my $type=pop(@keycomponents);
765: my $file=&unescape(pop(@keycomponents));
766: my $source = &unescape(join('___',@keycomponents));
1.5 matthew 767: $file = &unescape($file);
768: $value = &unescape($value);
769: if ($type =~ /^(avetries|count|difficulty|stdno|timestamp)$/) {
770: #
771: # Statistics: $source is course id
772: $DynamicData{$file}->{'statistics'}->{$source}->{$type}=$value;
1.11 www 773: } elsif ($type =~ /^(clear|comments|depth|technical|helpful|correct)$/){
1.5 matthew 774: #
775: # Evaluation $source is username, check if they evaluated it
776: # more than once. If so, pad the entry with a space.
777: while(exists($DynamicData{$file}->{'evaluation'}->{$type}->{$source})) {
778: $source .= ' ';
779: }
780: $DynamicData{$file}->{'evaluation'}->{$type}->{$source}=$value;
781: } elsif ($type =~ /^(course|comefrom|goto|usage)$/) {
782: #
783: # Context $source is course id or resource
784: push(@{$DynamicData{$file}->{$type}},&unescape($source));
785: } elsif ($type eq 'stats') {
786: #
787: # Statistics storage...
788: # $source is $cid\_$sec\_$stdno
789: # $value is stat1=value&stat2=value&stat3=value,....
790: #
1.38 ! www 791: my ($cid,$sec,$stdno,$part,$instance)=split('&',$source);
! 792: my $datakey = $cid.'&'.$sec.'&'.$part.'&'.$instance;
1.5 matthew 793: my @Data = split('&',$value);
794: my %Statistics;
795: while (my ($key,$value) = split('=',pop(@Data))) {
796: $Statistics{$key} = $value;
797: }
1.8 matthew 798: $sec =~ s:("$|^")::g;
799: $Statistics{'sections'} = $sec;
1.5 matthew 800: #
801: # Only store the data if the number of students is greater
802: # than the data already stored
1.38 ! www 803: if (! exists($DynamicData{$file}->{'stats'}->{$datakey}) ||
! 804: $DynamicData{$file}->{'stats'}->{$datakey}->{'stdno'}<$stdno){
! 805: $DynamicData{$file}->{'stats'}->{$datakey}=\%Statistics;
1.5 matthew 806: }
807: }
808: }
809: return %DynamicData;
810: }
811:
812:
813: ######################################################################
814: ######################################################################
815:
816: =pod
817:
818: =item &process_dynamic_metadata
819:
820: Inputs: $url: the url of the item to process
821: $DynamicData: hash reference for the results of &process_reseval_data
822:
823: Returns: Hash containing the following keys:
824: avetries, avetries_list, difficulty, difficulty_list, stdno, stdno_list,
825: course, course_list, goto, goto_list, comefrom, comefrom_list,
826: usage, clear, technical, correct, helpful, depth, comments
827:
828: Each of the return keys is associated with either a number or a string
829: The *_list items are comma-seperated strings. 'comments' is a string
830: containing generically marked-up comments.
831:
832: =cut
833:
834: ######################################################################
835: ######################################################################
836: sub process_dynamic_metadata {
837: my ($url,$DynamicData) = @_;
838: my %data;
839: my $resdata = $DynamicData->{$url};
840: #
1.8 matthew 841: # Get the statistical data - Use a weighted average
842: foreach my $type (qw/avetries difficulty disc/) {
843: my $studentcount;
1.21 albertel 844: my %course_counted;
1.5 matthew 845: my $sum;
846: my @Values;
1.8 matthew 847: my @Students;
1.5 matthew 848: #
1.21 albertel 849: # New data
1.8 matthew 850: if (exists($resdata->{'stats'})) {
851: foreach my $identifier (sort(keys(%{$resdata->{'stats'}}))) {
852: my $coursedata = $resdata->{'stats'}->{$identifier};
1.21 albertel 853: next if (lc($coursedata->{$type}) eq 'nan');
854: $course_counted{$coursedata->{'course'}}++;
1.8 matthew 855: $studentcount += $coursedata->{'stdno'};
856: $sum += $coursedata->{$type}*$coursedata->{'stdno'};
857: push(@Values,$coursedata->{$type});
858: push(@Students,$coursedata->{'stdno'});
859: }
860: }
861: #
1.21 albertel 862: # Old data
863: foreach my $course (keys(%{$resdata->{'statistics'}})) {
864: next if (exists($course_counted{$course}));
865: my $coursedata = $resdata->{'statistics'}{$course};
866: if (ref($coursedata) eq 'HASH' && exists($coursedata->{$type})) {
867: next if (lc($coursedata->{$type}) eq 'nan');
868: $studentcount += $coursedata->{'stdno'};
869: $sum += ($coursedata->{$type}*$coursedata->{'stdno'});
870: push(@Values,$coursedata->{$type});
871: push(@Students,$coursedata->{'stdno'});
872: }
873: }
1.8 matthew 874: if (defined($studentcount) && $studentcount>0) {
875: $data{$type} = $sum/$studentcount;
1.5 matthew 876: $data{$type.'_list'} = join(',',@Values);
877: }
878: }
879: #
1.8 matthew 880: # Find out the number of students who have completed the resource...
881: my $stdno;
1.20 albertel 882: my %course_counted;
1.8 matthew 883: if (exists($resdata->{'stats'})) {
884: #
885: # For the number of students, take the maximum found for the class
886: my $current_course;
887: my $coursemax=0;
888: foreach my $identifier (sort(keys(%{$resdata->{'stats'}}))) {
889: my $coursedata = $resdata->{'stats'}->{$identifier};
890: if (! defined($current_course)) {
891: $current_course = $coursedata->{'course'};
892: }
893: if ($current_course ne $coursedata->{'course'}) {
894: $stdno += $coursemax;
1.20 albertel 895: $course_counted{$coursedata->{'course'}}++;
1.8 matthew 896: $coursemax = 0;
897: $current_course = $coursedata->{'course'};
898: }
899: if ($coursemax < $coursedata->{'stdno'}) {
900: $coursemax = $coursedata->{'stdno'};
901: }
902: }
903: $stdno += $coursemax; # pick up the final course in the list
904: }
1.20 albertel 905: # check for old data that has not been run since the format was changed
906: foreach my $course (keys(%{$resdata->{'statistics'}})) {
907: next if (exists($course_counted{$course}));
908: my $coursedata = $resdata->{'statistics'}{$course};
909: if (ref($coursedata) eq 'HASH' && exists($coursedata->{'stdno'})) {
910: $stdno += $coursedata->{'stdno'};
911: }
912: }
1.8 matthew 913: $data{'stdno'}=$stdno;
914: #
1.5 matthew 915: # Get the context data
916: foreach my $type (qw/course goto comefrom/) {
917: if (defined($resdata->{$type}) &&
918: ref($resdata->{$type}) eq 'ARRAY') {
919: $data{$type} = scalar(@{$resdata->{$type}});
920: $data{$type.'_list'} = join(',',@{$resdata->{$type}});
921: }
922: }
1.35 www 923: #
924: # NOTE: usage is named sequsage elsewhere in LON-CAPA
925: # The translation happens here
926: #
1.5 matthew 927: if (defined($resdata->{'usage'}) &&
928: ref($resdata->{'usage'}) eq 'ARRAY') {
929: $data{'sequsage'} = scalar(@{$resdata->{'usage'}});
930: $data{'sequsage_list'} = join(',',@{$resdata->{'usage'}});
931: }
932: #
933: # Get the evaluation data
934: foreach my $type (qw/clear technical correct helpful depth/) {
935: my $count;
936: my $sum;
937: foreach my $evaluator (keys(%{$resdata->{'evaluation'}->{$type}})){
938: $sum += $resdata->{'evaluation'}->{$type}->{$evaluator};
939: $count++;
940: }
941: if ($count > 0) {
942: $data{$type}=$sum/$count;
943: }
944: }
945: #
946: # put together comments
1.26 bisitz 947: my $comments = '';
1.5 matthew 948: foreach my $evaluator (keys(%{$resdata->{'evaluation'}->{'comments'}})){
1.7 matthew 949: $comments .=
950: '<p>'.
1.26 bisitz 951: '<b>'.$evaluator.'</b>: '.
1.7 matthew 952: $resdata->{'evaluation'}->{'comments'}->{$evaluator}.
953: '</p>';
1.5 matthew 954: }
1.26 bisitz 955: if ($comments) {
956: $comments = '<div class="LCevalcomments">'
957: .$comments
958: .'</div>';
1.27 bisitz 959: $data{'comments'} = $comments;
1.26 bisitz 960: }
1.5 matthew 961: #
1.8 matthew 962: if (exists($resdata->{'stats'})) {
963: $data{'stats'} = $resdata->{'stats'};
964: }
1.12 matthew 965: if (exists($DynamicData->{'domain'})) {
966: $data{'domain'} = $DynamicData->{'domain'};
967: }
1.8 matthew 968: #
1.5 matthew 969: return %data;
970: }
971:
1.8 matthew 972: sub dynamic_metadata_storage {
973: my ($data) = @_;
974: my %Store;
975: my $courseid = $data->{'course'};
976: my $sections = $data->{'sections'};
977: my $numstu = $data->{'num_students'};
1.36 www 978: my $part = $data->{'part'};
979: my $symb = $data->{'symb'};
1.38 ! www 980: my $key = $courseid.'&'.$sections.'&'.$numstu.'&'.$part.'&'.$symb.'___stats';
1.8 matthew 981: $Store{$key} =
982: 'course='.$courseid.'&'.
983: 'sections='.$sections.'&'.
984: 'timestamp='.time.'&'.
1.36 www 985: 'part='.$part.'&'.
986: 'stdno='.$numstu.'&'.
1.8 matthew 987: 'avetries='.$data->{'mean_tries'}.'&'.
1.36 www 988: 'difficulty='.$data->{'deg_of_diff'}.'&'.
989: 'disc='.$data->{'deg_of_disc'};
1.8 matthew 990: return %Store;
991: }
1.6 matthew 992:
1.16 raeburn 993: ###############################################################
994: ###############################################################
995: ### ###
996: ### &portfolio_metadata($filepath,$dom,$uname,$group) ###
997: ### Retrieve metadata for the given file ###
998: ### Returns array - ###
999: ### contains reference to metadatahash and ###
1000: ### optional reference to addedfields hash ###
1001: ### ###
1002: ###############################################################
1003: ###############################################################
1004:
1005: sub portfolio_metadata {
1006: my ($fullpath,$dom,$uname,$group)=@_;
1007: my ($mime) = ( $fullpath=~/\.(\w+)$/ );
1008: my %metacache=();
1009: if ($fullpath !~ /\.meta$/) {
1010: $fullpath .= '.meta';
1011: }
1012: my (@standard_fields,%addedfields);
1013: my $colsref = $Portfolio_metadata_table_description;
1014: if (ref($colsref) eq 'ARRAY') {
1015: my @columns = @{$colsref};
1016: foreach my $coldata (@columns) {
1017: push(@standard_fields,$coldata->{'name'});
1018: }
1019: }
1020: my $metastring=&getfile($fullpath);
1021: if (! defined($metastring)) {
1022: $metacache{'keys'}= 'owner,domain,mime';
1023: $metacache{'owner'} = $uname.':'.$dom;
1024: $metacache{'domain'} = $dom;
1025: $metacache{'mime'} = $mime;
1026: if ($group ne '') {
1027: $metacache{'keys'} .= ',courserestricted';
1028: $metacache{'courserestricted'} = 'course.'.$dom.'_'.$uname;
1029: }
1030: } else {
1031: my $parser=HTML::TokeParser->new(\$metastring);
1032: my $token;
1033: while ($token=$parser->get_token) {
1034: if ($token->[0] eq 'S') {
1035: my $entry=$token->[1];
1036: if ($metacache{'keys'}) {
1037: $metacache{'keys'}.=','.$entry;
1038: } else {
1039: $metacache{'keys'}=$entry;
1040: }
1041: my $value = $parser->get_text('/'.$entry);
1042: if (!grep(/^\Q$entry\E$/,@standard_fields)) {
1043: my $clean_value = lc($value);
1044: $clean_value =~ s/\s/_/g;
1045: if ($clean_value ne $entry) {
1046: if (defined($addedfields{$entry})) {
1047: $addedfields{$entry} .=','.$value;
1048: } else {
1049: $addedfields{$entry} = $value;
1050: }
1051: }
1052: } else {
1053: $metacache{$entry} = $value;
1054: }
1055: }
1056: } # End of ($token->[0] eq 'S')
1.22 albertel 1057:
1058: if (!exists($metacache{'domain'})) {
1059: $metacache{'domain'} = $dom;
1060: }
1.16 raeburn 1061: }
1062: return (\%metacache,$metacache{'courserestricted'},\%addedfields);
1063: }
1064:
1065: sub process_portfolio_access_data {
1066: my ($dbh,$simulate,$newnames,$url,$fullpath,$access_hash,$caller) = @_;
1067: my %loghash;
1068: if ($caller eq 'update') {
1069: # Delete old data (no error if deleting non-existent record).
1.23 raeburn 1070: my $error;
1071: if ($url eq '') {
1072: $error = 'No url specified';
1073: } else {
1074: my $delitem = 'url = '.$dbh->quote($url);
1075: $error=&delete_metadata($dbh,$newnames->{'access'},$delitem);
1076: }
1.16 raeburn 1077: if (defined($error)) {
1078: $loghash{'access'}{'err'} = "MySQL Error Delete: ".$error;
1079: return %loghash;
1080: }
1081: }
1082: # Check the file exists
1083: if (-e $fullpath) {
1084: foreach my $key (keys(%{$access_hash})) {
1085: my $acc_data;
1086: $acc_data->{url} = $url;
1087: $acc_data->{keynum} = $key;
1088: my ($num,$scope,$end,$start) =
1089: ($key =~ /^([^:]+):([a-z]+)_(\d*)_?(\d*)$/);
1090: next if (($scope ne 'public') && ($scope ne 'guest'));
1091: $acc_data->{scope} = $scope;
1.28 raeburn 1092: my $sqltime_error;
1.16 raeburn 1093: if ($end != 0) {
1.28 raeburn 1094: $acc_data->{end} = &sqltime($end,\$sqltime_error);
1095: }
1096: $acc_data->{start} = &sqltime($start,\$sqltime_error);
1097: if ($sqltime_error) {
1098: $loghash{$key}{'err'} = $sqltime_error;
1.16 raeburn 1099: }
1100: if (! $simulate) {
1101: my ($count,$err) =
1102: &store_metadata($dbh,$newnames->{'access'},
1103: 'portfolio_access',$acc_data);
1104: if ($err) {
1105: $loghash{$key}{'err'} = "MySQL Error Insert: ".$err;
1106: }
1107: if ($count < 1) {
1108: $loghash{$key}{'count'} =
1109: "Unable to insert record into MySQL database for $url";
1110: }
1111: }
1112: }
1113: }
1114: return %loghash;
1115: }
1116:
1117: sub process_portfolio_metadata {
1118: my ($dbh,$simulate,$newnames,$url,$fullpath,$is_course,$dom,$uname,$group,$caller) = @_;
1119: my %loghash;
1120: if ($caller eq 'update') {
1121: # Delete old data (no error if deleting non-existent record).
1.23 raeburn 1122: my ($error,$delitem);
1123: if ($url eq '') {
1124: $error = 'No url specified';
1125: } else {
1126: $delitem = 'url = '.$dbh->quote($url);
1127: $error=&delete_metadata($dbh,$newnames->{'portfolio'},$delitem);
1128: }
1.16 raeburn 1129: if (defined($error)) {
1130: $loghash{'metadata'}{'err'} = "MySQL Error delete metadata: ".
1131: $error;
1132: return %loghash;
1133: }
1.23 raeburn 1134: $error=&delete_metadata($dbh,$newnames->{'addedfields'},$delitem);
1.16 raeburn 1135: if (defined($error)) {
1136: $loghash{'addedfields'}{'err'}="MySQL Error delete addedfields: ".$error;
1137: }
1138: }
1139: # Check the file exists.
1140: if (-e $fullpath) {
1141: my ($ref,$crs,$addedfields) = &portfolio_metadata($fullpath,$dom,$uname,
1142: $group);
1.28 raeburn 1143: my $sqltime_error;
1144: &getfiledates($ref,$fullpath,\$sqltime_error);
1.16 raeburn 1145: if ($is_course) {
1146: $ref->{'groupname'} = $group;
1147: }
1148: my %Data;
1149: if (ref($ref) eq 'HASH') {
1150: %Data = %{$ref};
1151: }
1152: %Data = (
1153: %Data,
1154: 'url'=>$url,
1155: 'version'=>'current',
1156: );
1157: my %loghash;
1158: if (! $simulate) {
1.28 raeburn 1159: if ($sqltime_error) {
1160: $loghash{'metadata'."\0"}{'err'} = $sqltime_error;
1161: }
1.16 raeburn 1162: my ($count,$err) =
1163: &store_metadata($dbh,$newnames->{'portfolio'},'portfolio_metadata',
1164: \%Data);
1165: if ($err) {
1166: $loghash{'metadata'."\0"}{'err'} = "MySQL Error Insert: ".$err;
1167: }
1168: if ($count < 1) {
1169: $loghash{'metadata'."\0"}{'count'} = "Unable to insert record into MySQL portfolio_metadata database table for $url";
1170: }
1171: if (ref($addedfields) eq 'HASH') {
1172: if (keys(%{$addedfields}) > 0) {
1173: foreach my $key (keys(%{$addedfields})) {
1174: my $added_data = {
1175: 'url' => $url,
1176: 'field' => $key,
1177: 'value' => $addedfields->{$key},
1178: 'courserestricted' => $crs,
1179: };
1180: my ($count,$err) =
1181: &store_metadata($dbh,$newnames->{'addedfields'},
1182: 'portfolio_addedfields',$added_data);
1183: if ($err) {
1184: $loghash{$key}{'err'} =
1185: "MySQL Error Insert: ".$err;
1186: }
1187: if ($count < 1) {
1188: $loghash{$key}{'count'} = "Unable to insert record into MySQL portfolio_addedfields database table for url = $url and field = $key";
1189: }
1190: }
1191: }
1192: }
1193: }
1194: }
1195: return %loghash;
1196: }
1197:
1.23 raeburn 1198: sub process_allusers_data {
1199: my ($dbh,$simulate,$newnames,$uname,$udom,$userdata,$caller) = @_;
1200: my %loghash;
1201: if ($caller eq 'update') {
1202: # Delete old data (no error if deleting non-existent record).
1203: my ($error,$delitem);
1204: if ($udom eq '' || $uname eq '' ) {
1205: $error = 'No domain and/or username specified';
1206: } else {
1.25 raeburn 1207: $delitem = 'domain = '.$dbh->quote($udom).' AND username '.
1208: 'COLLATE latin1_general_cs = '.$dbh->quote($uname);
1.23 raeburn 1209: $error=&delete_metadata($dbh,$newnames->{'allusers'},$delitem);
1210: }
1211: if (defined($error)) {
1212: $loghash{'err'} = 'MySQL Error in allusers delete: '.$error;
1213: return %loghash;
1214: }
1215: }
1216: if (!$simulate) {
1217: if ($udom ne '' && $uname ne '') {
1218: my ($count,$err) = &store_metadata($dbh,$newnames->{'allusers'},
1219: 'allusers',$userdata);
1220: if ($err) {
1221: $loghash{'err'} = 'MySQL Error in allusers insert: '.$err;
1222: }
1223: if ($count < 1) {
1224: $loghash{'count'} =
1225: 'Unable to insert record into MySQL allusers database for '.
1226: $uname.' in '.$udom;
1227: }
1228: } else {
1229: $loghash{'err'} =
1230: 'MySQL Error allusrs insert: missing username and/or domain';
1231: }
1232: }
1233: return %loghash;
1234: }
1235:
1.5 matthew 1236: ######################################################################
1237: ######################################################################
1.14 raeburn 1238:
1.16 raeburn 1239: sub getfile {
1240: my $file = shift();
1241: if (! -e $file ) {
1242: return undef;
1243: }
1.17 albertel 1244: open(my $fh,"<$file");
1.16 raeburn 1245: my $contents = '';
1246: while (<$fh>) {
1247: $contents .= $_;
1248: }
1249: return $contents;
1250: }
1251:
1252: ##
1.28 raeburn 1253: ## &getfiledates($ref,$target,$sqltime_error)
1.16 raeburn 1254: ## Converts creationdate and modifieddates to SQL format
1255: ## Applies stat() to file to retrieve dates if missing
1256: sub getfiledates {
1.28 raeburn 1257: my ($ref,$target,$sqltime_error) = @_;
1.16 raeburn 1258: if (! defined($ref->{'creationdate'}) ||
1259: $ref->{'creationdate'} =~ /^\s*$/) {
1260: $ref->{'creationdate'} = (stat($target))[9];
1261: }
1262: if (! defined($ref->{'lastrevisiondate'}) ||
1263: $ref->{'lastrevisiondate'} =~ /^\s*$/) {
1264: $ref->{'lastrevisiondate'} = (stat($target))[9];
1265: }
1.28 raeburn 1266: $ref->{'creationdate'} = &sqltime($ref->{'creationdate'},$sqltime_error);
1267: $ref->{'lastrevisiondate'} = &sqltime($ref->{'lastrevisiondate'},$sqltime_error);
1.16 raeburn 1268: }
1269:
1.15 raeburn 1270: ##
1.28 raeburn 1271: ## &sqltime($timestamp,$sqltime_error)
1.15 raeburn 1272: ##
1273: ## Convert perl $timestamp to MySQL time. MySQL expects YYYY-MM-DD HH:MM:SS
1274: ##
1275: sub sqltime {
1.28 raeburn 1276: my ($time,$sqltime_error) = @_;
1.15 raeburn 1277: my $mysqltime;
1278: if ($time =~
1279: /(\d+)-(\d+)-(\d+) # YYYY-MM-DD
1280: \s # a space
1281: (\d+):(\d+):(\d+) # HH:MM::SS
1282: /x ) {
1283: # Some of the .meta files have the time in mysql
1284: # format already, so just make sure they are 0 padded and
1285: # pass them back.
1286: $mysqltime = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
1287: $1,$2,$3,$4,$5,$6);
1288: } elsif ($time =~ /^\d+$/) {
1289: my @TimeData = gmtime($time);
1290: # Alter the month to be 1-12 instead of 0-11
1291: $TimeData[4]++;
1292: # Alter the year to be from 0 instead of from 1900
1293: $TimeData[5]+=1900;
1294: $mysqltime = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
1295: @TimeData[5,4,3,2,1,0]);
1296: } elsif (! defined($time) || $time == 0) {
1297: $mysqltime = 0;
1298: } else {
1.28 raeburn 1299: if (ref($sqltime_error) eq 'SCALAR') {
1300: $$sqltime_error = "sqltime:Unable to decode time ".$time;
1301: }
1.15 raeburn 1302: $mysqltime = 0;
1303: }
1304: return $mysqltime;
1305: }
1.14 raeburn 1306:
1307: ######################################################################
1308: ######################################################################
1.5 matthew 1309: ##
1310: ## The usual suspects, repeated here to reduce dependency hell
1311: ##
1312: ######################################################################
1313: ######################################################################
1314: sub unescape {
1315: my $str=shift;
1316: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
1317: return $str;
1318: }
1319:
1320: sub escape {
1321: my $str=shift;
1322: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
1323: return $str;
1324: }
1.6 matthew 1325:
1.1 matthew 1326: 1;
1327:
1328: __END__;
1329:
1330: =pod
1331:
1332: =back
1333:
1334: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>