[system] / branches / gage_dev / webwork2 / lib / WeBWorK / Utils / CourseManagement.pm Repository:
ViewVC logotype

Diff of /branches/gage_dev/webwork2/lib/WeBWorK/Utils/CourseManagement.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 5206 Revision 5207
1################################################################################ 1################################################################################
2# WeBWorK Online Homework Delivery System 2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/ 3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork2/lib/WeBWorK/Utils/CourseManagement.pm,v 1.39 2007/03/06 01:33:53 sh002i Exp $ 4# $CVSHeader: webwork2/lib/WeBWorK/Utils/CourseManagement.pm,v 1.40 2007/06/25 12:10:49 gage Exp $
5# 5#
6# This program is free software; you can redistribute it and/or modify it under 6# This program is free software; you can redistribute it and/or modify it under
7# the terms of either: (a) the GNU General Public License as published by the 7# the terms of either: (a) the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any later 8# Free Software Foundation; either version 2, or (at your option) any later
9# version, or (b) the "Artistic License" which comes with this package. 9# version, or (b) the "Artistic License" which comes with this package.
282 courseID => $courseID, 282 courseID => $courseID,
283 ce => $ce, 283 ce => $ce,
284 dbOptions => $dbOptions, 284 dbOptions => $dbOptions,
285 newCourseID => $newCourseID, 285 newCourseID => $newCourseID,
286 286
287%options may also contain:
288
289 skipDBRename => $skipDBRename,
290
287Rename the course named $courseID to $newCourseID. 291Rename the course named $courseID to $newCourseID.
288 292
289$ce is a WeBWorK::CourseEnvironment object that describes the existing course's 293$ce is a WeBWorK::CourseEnvironment object that describes the existing course's
290environment. 294environment.
291 295
299If the course's database layout is C<sql_single> or C<sql_moodle>, new tables 303If the course's database layout is C<sql_single> or C<sql_moodle>, new tables
300are created in the current database, course data is copied from the old tables 304are created in the current database, course data is copied from the old tables
301to the new tables, and the old tables are deleted. 305to the new tables, and the old tables are deleted.
302 306
303If the course's database layout is something else, no database changes are made. 307If the course's database layout is something else, no database changes are made.
308
309If $skipDBRename is true, no database changes are made. This is useful if a
310course is being unarchived and no database was found, or for renaming the
311modelCourse.
304 312
305Any errors encountered while renaming the course are returned. 313Any errors encountered while renaming the course are returned.
306 314
307=cut 315=cut
308 316
319 327
320 my $oldCourseID = $options{courseID}; 328 my $oldCourseID = $options{courseID};
321 my $oldCE = $options{ce}; 329 my $oldCE = $options{ce};
322 my %dbOptions = defined $options{dbOptions} ? %{ $options{dbOptions} } : (); 330 my %dbOptions = defined $options{dbOptions} ? %{ $options{dbOptions} } : ();
323 my $newCourseID = $options{newCourseID}; 331 my $newCourseID = $options{newCourseID};
332 my $skipDBRename = $options{skipDBRename} || 0;
324 333
325 # get the database layout out of the options hash 334 # get the database layout out of the options hash
326 my $dbLayoutName = $oldCE->{dbLayoutName}; 335 my $dbLayoutName = $oldCE->{dbLayoutName};
327 336
328 # collect some data 337 # collect some data
420 } 429 }
421 } 430 }
422 431
423 ##### step 2: rename database ##### 432 ##### step 2: rename database #####
424 433
434 unless ($skipDBRename) {
425 my $oldDB = new WeBWorK::DB($oldCE->{dbLayouts}{$dbLayoutName}); 435 my $oldDB = new WeBWorK::DB($oldCE->{dbLayouts}{$dbLayoutName});
426 my $rename_db_result = $oldDB->rename_tables($newCE->{dbLayouts}{$dbLayoutName}); 436 my $rename_db_result = $oldDB->rename_tables($newCE->{dbLayouts}{$dbLayoutName});
427 die "$oldCourseID: course database renaming failed.\n" unless $rename_db_result; 437 die "$oldCourseID: course database renaming failed.\n" unless $rename_db_result;
438 }
428} 439}
429 440
430################################################################################ 441################################################################################
431 442
432=item deleteCourse(%options) 443=item deleteCourse(%options)
529 540
530=item archiveCourse(%options) 541=item archiveCourse(%options)
531 542
532%options must contain: 543%options must contain:
533 544
534 courseID => $courseID, 545 courseID => $courseID,
535 ce => $ce, 546 ce => $ce,
536 dbOptions => $dbOptions,
537 newCourseID => $newCourseID,
538 547
539Archive the course named $courseID in the $webworkDirs{courses} directory 548Creates a gzipped tar archive (.tar.gz) of the course $courseID in the WeBWorK
540as $webworkDirs{courses}/$courseID.tar.gz. The data from the database is 549courses directory. Before archiving, the course database is dumped into a
541stored in several files at $courseID/DATA/$table_name.txt before the course's directories 550subdirectory of the course's DATA directory.
542are tarred and gzipped. The table names are $courseID_user, $courseID_set 551
543and so forth. Only files and directories stored directly in the course directory 552Only files and directories stored directly in the course directory are archived.
544are archived. The contents of linked files is not archived although the symbolic links 553The contents of linked files is not archived although the symbolic links
545themselves are saved. 554themselves are saved.
546 555
556$courseID is the name of the course to archive.
557
547$ce is a WeBWorK::CourseEnvironment object that describes the existing course's 558$ce is a WeBWorK::CourseEnvironment object that describes the course's
548environment. 559environment. (This is used to access the course database and get path
560information.)
549 561
550$dbOptions is a reference to a hash containing information required to create 562If an error occurs, an exception is thrown.
551the course's new database and delete the course's old database. (Currently,
552no information is needed, so this should be an empty hash.)
553
554If the course's database layout is C<sql_single>, the contents of
555the courses database tables are exported to text files using the sql database's
556export facility. Then the tables are deleted from the database.
557
558If the course's database layout is something else, no database changes are made.
559
560Any errors encountered while renaming the course are returned.
561 563
562=cut 564=cut
563 565
564sub archiveCourse { 566sub archiveCourse {
565 my (%options) = @_; 567 my (%options) = @_;
566
567 # archiveCourseHelper needs:
568 # $fromCourseID ($oldCourseID)
569 # $fromCE ($ce)
570 # $toCourseID ($newCourseID)
571 # $toCE (construct from $ce)
572 # $dbLayoutName ($ce->{dbLayoutName})
573 # %options ($dbOptions)
574
575 my $courseID = $options{courseID}; 568 my $courseID = $options{courseID};
576 my $ce = $options{ce}; 569 my $ce = $options{ce};
577 my %dbOptions = defined $options{dbOptions} ? %{ $options{dbOptions} } : ();
578
579 570
580 # get the database layout out of the options hash 571 # make sure the user isn't brain damaged
581 my $dbLayoutName = $ce->{dbLayoutName}; 572 croak "The course environment supplied doesn't appear to describe the course $courseID. Can't proceed"
573 unless $ce->{courseName} eq $courseID;
582 574
583 if (not ref getHelperRef("archiveCourseHelper", $dbLayoutName)) { 575 # grab some values we'll need
584 die "This database layout doesn't support course archiving. Sorry!\n" 576 my $course_dir = $ce->{courseDirs}{root};
585 } 577 my $archive_path = $ce->{webworkDirs}{courses} . "/$courseID.tar.gz";
586
587 # collect some data
588 my $coursesDir = $ce->{webworkDirs}->{courses};
589 my $courseDir = "$coursesDir/$courseID";
590 my $dataDir = "$courseDir/DATA"; 578 my $data_dir = $ce->{courseDirs}{DATA};
591 my $archivePath = "$coursesDir/$courseID.tar.gz"; 579 my $dump_dir = "$data_dir/mysqldump";
592 580
593 # create DATA directory if it does not exist.
594 unless (-e $dataDir) {
595 mkdir "$dataDir" or die "Failed to create course directory $dataDir";
596 }
597 # fail if the target file already exists
598 if (-e $archivePath) {
599 croak "The course $courseID has already been archived at $archivePath";
600 }
601 581
602 # fail if the source course does not exist 582 # fail if the source course does not exist
603 unless (-e $courseDir) { 583 unless (-e $course_dir) {
604 croak "$courseID: course not found"; 584 croak "$courseID: course not found";
605 } 585 }
606 586
607 $dbOptions{archiveDatabasePath} = "$dataDir/${courseID}_mysql.database"; 587 # fail if a course archive already exists
608 ##### step 1: export database contents ###### 588 # FIXME there could be an option to overwrite an existing archive
609 # munge DB options to move new_database => database 589 if (-e $archive_path) {
610 590 croak "The course '$courseID' has already been archived at '$archive_path'.\n";
591 }
611 592
612 my $archiveHelperResult = archiveCourseHelper($courseID, $ce, $dbLayoutName, %dbOptions); 593 #### step 1: dump tables #####
613 die "$courseID: course database dump failed.\n" unless $archiveHelperResult; 594
614 595 unless (-e $dump_dir) {
596 mkdir $dump_dir or croak "Failed to create course database dump directory '$data_dir': $!";
597 }
598
599 my $db = new WeBWorK::DB($ce->{dbLayout});
600 my $dump_db_result = $db->dump_tables($dump_dir);
601 unless ($dump_db_result) {
602 _archiveCourse_remove_dump_dir($ce, $dump_dir);
603 croak "$courseID: course database dump failed.\n";
604 }
605
615 ##### step 2: tar and gzip course directory ##### 606 ##### step 2: tar and gzip course directory (including dumped database) #####
616 607
617 # archive top-level course directory 608 # we want tar to run from the parent directory of the course directory
609 my $chdir_to = "$course_dir/..";
610
618 my $tar_cmd = "2>&1"." ".$ce->{externalPrograms}{tar} 611 my $tar_cmd = "2>&1 " . $ce->{externalPrograms}{tar}
619 . " -C " . shell_quote($coursesDir) 612 . " -C " . shell_quote($chdir_to)
620 . " -czf " . shell_quote($archivePath) 613 . " -czf " . shell_quote($archive_path)
621 . " " . shell_quote($courseID); 614 . " " . shell_quote($courseID);
622 debug("archiving course dir: $tar_cmd\n");
623 my $tar_out = readpipe $tar_cmd; 615 my $tar_out = readpipe $tar_cmd;
624 if ($?) { 616 if ($?) {
625 my $exit = $? >> 8; 617 my $exit = $? >> 8;
626 my $signal = $? & 127; 618 my $signal = $? & 127;
627 my $core = $? & 128; 619 my $core = $? & 128;
620 _archiveCourse_remove_dump_dir($ce, $dump_dir);
628 die "Failed to archive course directory with command '$tar_cmd' (exit=$exit signal=$signal core=$core): $tar_out\n"; 621 croak "Failed to archive course directory '$course_dir' with command '$tar_cmd' (exit=$exit signal=$signal core=$core): $tar_out\n";
629 } 622 }
623
624 ##### step 3: remove database dump files from course directory #####
625
626 _archiveCourse_remove_dump_dir($ce, $dump_dir);
630} 627}
631 628
629sub _archiveCourse_remove_dump_dir {
630 my ($ce, $dump_dir) = @_;
631 my $rm_cmd = "2>&1 " . $ce->{externalPrograms}{rm}
632 . " -rf " . shell_quote($dump_dir);
633 my $rm_out = readpipe $rm_cmd;
634 if ($?) {
635 my $exit = $? >> 8;
636 my $signal = $? & 127;
637 my $core = $? & 128;
638 carp "Failed to remove course database dump directory '$dump_dir' with command '$rm_cmd' (exit=$exit signal=$signal core=$core): $rm_out\n";
639 }
640}
641
632################################################################################ 642################################################################################
643
644=item unarchiveCourse(%options)
645
646%options must contain:
647
648 oldCourseID => $oldCourseID,
649 archivePath => $archivePath,
650 ce => $ce,
651
652%options may also contain:
653
654 newCourseID => $newCourseID,
655
656Restores course $oldCourseID from a gzipped tar archive (.tar.gz) located at
657$archivePath. After unarchiving, the course database is restored from a
658subdirectory of the course's DATA directory.
659
660If $newCourseID is defined and differs from $oldCourseID, the course is renamed
661after unarchiving.
662
663$ce is a WeBWorK::CourseEnvironment object that describes the some course's
664environment. (Usually this would be the admin course.) This is used to access
665the course database and get path information.
666
667If an error occurs, an exception is thrown.
668
669=cut
633 670
634sub unarchiveCourse { 671sub unarchiveCourse {
635 my (%options) = @_; 672 my (%options) = @_;
636 673
637 # renameCourseHelper needs:
638 # $fromCourseID ($oldCourseID)
639 # $fromCE ($ce)
640 # $toCourseID ($newCourseID)
641 # $toCE (construct from $ce)
642 # $dbLayoutName ($ce->{dbLayoutName})
643 # %options ($dbOptions)
644
645 my $newCourseID = $options{newCourseID}; 674 my $newCourseID = $options{newCourseID};
646 my $oldCourseID = $options{oldCourseID}; 675 my $currCourseID = $options{oldCourseID};
647 my $archivePath = $options{archivePath}; 676 my $archivePath = $options{archivePath};
648 my $ce = $options{ce}; 677 my $ce = $options{ce};
649 my %dbOptions = defined $options{dbOptions} ? %{ $options{dbOptions} } : (); 678
650 my $coursesDir = $ce->{webworkDirs}->{courses}; 679 my $coursesDir = $ce->{webworkDirs}{courses};
651 680
652 # Double check that the new course does not exist 681 # Double check that the new course does not exist
653 if (-e "$coursesDir/$newCourseID") { 682 if (-e "$coursesDir/$newCourseID") {
654 die "Cannot overwrite existing course $coursesDir/$newCourseID"; 683 die "Cannot overwrite existing course $coursesDir/$newCourseID";
655 } 684 }
656 my $restoreCourseData = undef;
657 ##
658 # Temporarily rename the old course if it exists -- saving data
659 ##
660 if (-e "$coursesDir/$oldCourseID") {
661 my $tmpCourseID = "${oldCourseID}_tmp";
662
663 debug("Moving $oldCourseID to $tmpCourseID");
664 my $tmpce = WeBWorK::CourseEnvironment->new(
665 $ce->{webworkDirs}->{root},
666 $ce->{webworkURLs}->{root},
667 $ce->{pg}->{directories}->{root},
668 $tmpCourseID,
669 );
670 $restoreCourseData = {
671 courseID => $tmpCourseID, # data for restoring from tmpCourse
672 ce => $tmpce,
673 dbOptions => undef,
674 newCourseID => $oldCourseID,
675 };
676 renameCourse(
677 courseID => $oldCourseID,
678 ce => WeBWorK::CourseEnvironment->new(
679 $ce->{webworkDirs}->{root},
680 $ce->{webworkURLs}->{root},
681 $ce->{pg}->{directories}->{root},$oldCourseID,
682 ),
683 dbOptions => undef,
684 newCourseID => $tmpCourseID,
685 );
686 }
687 ##
688 # Unarchive the old course
689 ##
690 685
686 ##### step 1: move a conflicting course away #####
691 687
692 my $courseID = $oldCourseID; 688 # if this function returns undef, it means there was no course in the way
693 ############################################################### 689 my $restoreCourseData = _unarchiveCourse_move_away($ce, $currCourseID);
694 # RPC call to tar and gzip the courses directory 690
695 ############################################################### 691 ##### step 2: crack open the tarball #####
692
696 my $tar_cmd = "2>&1"." ".$ce->{externalPrograms}{tar} 693 my $tar_cmd = "2>&1 " . $ce->{externalPrograms}{tar}
697 . " -C " . shell_quote($coursesDir) 694 . " -C " . shell_quote($coursesDir)
698 . " -xzf " . shell_quote($archivePath); 695 . " -xzf " . shell_quote($archivePath);
699 debug("unarchiving course dir: $tar_cmd\n");
700 my $tar_out = readpipe $tar_cmd; 696 my $tar_out = readpipe $tar_cmd;
701 if ($?) { 697 if ($?) {
702 my $exit = $? >> 8; 698 my $exit = $? >> 8;
703 my $signal = $? & 127; 699 my $signal = $? & 127;
704 my $core = $? & 128; 700 my $core = $? & 128;
701 _unarchiveCourse_move_back($restoreCourseData);
705 die "Failed to unarchive course directory with command '$tar_cmd' (exit=$exit signal=$signal core=$core): $tar_out\n"; 702 die "Failed to unarchive course directory with command '$tar_cmd' (exit=$exit signal=$signal core=$core): $tar_out\n";
706 } 703 }
707 ###############################################################
708 # End RPC call to tar and gzip the courses directory
709 ###############################################################
710 704
711 # read the global.conf and course.conf files for the newly created course 705 ##### step 3: read the course environment for this course #####
712 debug( "Checking that course directory is at $coursesDir/$courseID: = ", -e "$coursesDir/$courseID"); 706
713 my $ce2 = WeBWorK::CourseEnvironment->new( 707 my $ce2 = new WeBWorK::CourseEnvironment(
708 $ce->{webworkDirs}{root},
709 $ce->{webworkURLs}{root},
710 $ce->{pg}{directories}{root},
711 $currCourseID,
712 );
713
714 # pull out some useful stuff
715 my $course_dir = $ce2->{courseDirs}{root};
716 my $data_dir = $ce2->{courseDirs}{DATA};
717 my $dump_dir = "$data_dir/mysqldump";
718 my $old_dump_file = "$data_dir/${currCourseID}_mysql.database";
719
720 ##### step 4: restore the database tables #####
721
722 my $no_database;
723 my $restore_db_result = 1;
724 if (-e $dump_dir) {
725 my $db = new WeBWorK::DB($ce2->{dbLayout});
726 $restore_db_result = $db->restore_tables($dump_dir);
727 } elsif (-e $old_dump_file) {
728 my $dbLayoutName = $ce2->{dbLayoutName};
729 if (ref getHelperRef("unarchiveCourseHelper", $dbLayoutName)) {
730 eval {
731 $restore_db_result = unarchiveCourseHelper($currCourseID, $ce2, $dbLayoutName,
732 unarchiveDatabasePath=>$old_dump_file);
733 };
734 if ($@) {
735 warn "failed to unarchive course database from dump file '$old_dump_file: $@\n";
736 }
737 } else {
738 warn "course '$currCourseID' uses dbLayout '$dbLayoutName', which doesn't support restoring database tables. database tables will not be restored.\n";
739 $no_database = 1;
740 }
741 } else {
742 warn "course '$currCourseID' has no database dump in its data directory (checked for $dump_dir and $old_dump_file). database tables will not be restored.\n";
743 $no_database = 1;
744 }
745
746 unless ($restore_db_result) {
747 warn "database restore of course '$currCourseID' failed: the course will probably not be usable.\n";
748 }
749
750 ##### step 5: delete dump_dir and/or old_dump_file #####
751
752 if (-e $dump_dir) {
753 _archiveCourse_remove_dump_dir($ce, $dump_dir);
754 }
755 if (-e $old_dump_file) {
756 unlink $old_dump_file or carp "Failed to unlink course database dump file '$old_dump_file: $_\n";
757 }
758
759 ##### step 6: rename course #####
760
761 if (defined $newCourseID and $newCourseID ne $currCourseID) {
762 renameCourse(
763 courseID => $currCourseID,
764 ce => $ce2,
765 newCourseID => $newCourseID,
766 skipDBRename => $no_database,
767 );
768 }
769
770 ##### step 7: return conflicting course to its rightful place #####
771
772 _unarchiveCourse_move_back($restoreCourseData);
773}
774
775sub _unarchiveCourse_move_away {
776 my ($ce, $courseID) = @_;
777
778 # course environment for before the course is moved
779 my $ce2 = new WeBWorK::CourseEnvironment(
714 $ce->{webworkDirs}->{root}, 780 $ce->{webworkDirs}->{root},
715 $ce->{webworkURLs}->{root}, 781 $ce->{webworkURLs}->{root},
716 $ce->{pg}->{directories}->{root}, 782 $ce->{pg}->{directories}->{root},
717 $courseID, 783 $courseID,
718 ); 784 );
719 my $courseDir = "$coursesDir/$courseID";
720 my $dataDir = "$courseDir/DATA";
721
722 #get the database layout out of the options hash
723 my $dbLayoutName = $ce2->{dbLayoutName};
724
725 if (not ref getHelperRef("unarchiveCourseHelper", $dbLayoutName)) {
726 die "This database layout doesn't support course archiving. Sorry!\n"
727 }
728 $dbOptions{unarchiveDatabasePath} = "$dataDir/${courseID}_mysql.database";
729 # import database tables
730 my $unarchiveHelperResult = unarchiveCourseHelper($courseID, $ce, $dbLayoutName, %dbOptions);
731 die "$courseID: unable to import tables into database.\n" unless $unarchiveHelperResult;
732 785
733 ## 786 # if course directory doesn't exist, we don't have to do anything
734 # Change the unarchived course to the new course name if they are different 787 return unless -e $ce2->{courseDirs}{root};
735 ## 788
736 if ($courseID ne $newCourseID) { 789 # temporary name for course
737 790 my $tmpCourseID = "${courseID}_tmp";
738 debug("rename $courseID to $newCourseID"); 791
792 debug("Temporarily moving $courseID to $tmpCourseID to make room for course unarchiving");
793 renameCourse(
794 courseID => $courseID,
795 ce => $ce2,
796 newCourseID => $tmpCourseID,
797 );
798
799 # course environment for after the course is moved
739 my $oldce = WeBWorK::CourseEnvironment->new( 800 my $ce3 = new WeBWorK::CourseEnvironment(
740 $ce->{webworkDirs}->{root}, 801 $ce->{webworkDirs}->{root},
741 $ce->{webworkURLs}->{root}, 802 $ce->{webworkURLs}->{root},
742 $ce->{pg}->{directories}->{root}, 803 $ce->{pg}->{directories}->{root},
743 $courseID, 804 $tmpCourseID,
744 ); 805 );
745 renameCourse( 806
807 # data to pass to renameCourse when moving the course back to it's original name
808 my $restore_course_data = {
746 courseID => $courseID, 809 courseID => $tmpCourseID,
747 ce => $oldce, 810 ce => $ce3, # course environment for moved course
748 dbOptions => undef,
749 newCourseID => $newCourseID, 811 newCourseID => $courseID,
750 );
751 } 812 };
752 if (defined($restoreCourseData) ) {
753 813
754 ## 814 return $restore_course_data;
755 # Rename the temporary old course back to old course 815}
756 ##
757 debug("rename ".$restoreCourseData->{courseID}. " to ". $restoreCourseData->{newCourseID});
758 renameCourse(
759 %{$restoreCourseData}
760 );
761 816
762 } 817sub _unarchiveCourse_move_back {
818 my ($restore_course_data) = @_;
819
820 return unless $restore_course_data;
821
822 debug("Moving $$restore_course_data{courseID} back to $$restore_course_data{newCourseID} after course unarchiving");
823 renameCourse(%$restore_course_data);
763} 824}
764 825
765################################################################################ 826################################################################################
766 827
767=item dbLayoutSQLSources($dbLayout) 828=item dbLayoutSQLSources($dbLayout)

Legend:
Removed from v.5206  
changed lines
  Added in v.5207

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9