From 3f758aad1c619f5057e11b885e8b6ce804ca9e71 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 5 Aug 2026 17:36:51 -0500 Subject: [PATCH 1/2] Show both the course id and the archive file when listing course archives in the admin course. The previous setup did not take into account what would happen if multiple archive files contained the same course id. So instead of the `listArchivedCourse` method returning a hash of the form ```perl { myTestCourse => { filename => 'myTestCourse.tar.gz', size => '605 KB' } } ``` it now returns a hash of the form ```perl { 'myTestCourse.tar.gz' => { courseID => 'myTestCourse', size => '605 KB', lastModified => 1778667472 } } ``` Note that the `lastModified` key is only included because that is what is saved in the cache file, and that is just directly returned if the archive file has not been modified. Although, at this point it is not used elsewhere. Both the courseID and the filename are then displayed. The format displayed is `myTestCourse (myTestCourse.tar.gz, 605 KB)`. If there is also an archive file `myTestCourseAlt.tar.gz` then it will also be listed as something like`myTestCourse (myTestCourseAlt.tar.gz, 1.6 MB)`. --- lib/WeBWorK/Utils/CourseManagement.pm | 41 +++++++------------ .../ContentGenerator/CourseAdmin.html.ep | 2 +- .../CourseAdmin/unarchive_course_form.html.ep | 10 ++--- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 514d086132..4563b0514a 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -111,14 +111,15 @@ sub listCourses { =item listArchivedCourses($ce) Lists the courses which have been archived (end in .tar.gz). The courses found -are returned as a hash whose keys are the course ids and the values are -references to hashes containing the C (the basename of the file -including the .tar.gz extension) and file C. For example, +are returned as a hash whose keys are the filenames (the basename of the file +including the .tar.gz extension) and the values are references to hashes +containing the C, file C, and C time. For example, { - myTestCourse => { - filename => 'myTestCourse.tar.gz', - size => '605 KB' + 'myTestCourse.tar.gz' => { + courseID => 'myTestCourse', + size => '605 KB', + lastModified => 1778667472 } } @@ -134,23 +135,19 @@ sub listArchivedCourses { my $archiveDataFile = $archivesDir->child('archive-cache.json'); my $archiveData = eval { decode_json($archiveDataFile->slurp) } || {}; - my $archiveDataUpdated = 0; + my %updatedArchiveData; my %return; for (@$archives) { my $basename = $_->basename; my $lastModified = $_->stat->mtime; if ($archiveData->{$basename} && $archiveData->{$basename}{lastModified} >= $lastModified) { - $return{ $archiveData->{$basename}{courseID} } = { - filename => $basename, - size => $archiveData->{$basename}{size} - } - if defined $archiveData->{$basename}{courseID}; + $updatedArchiveData{$basename} = $archiveData->{$basename}; + $return{$basename} = $updatedArchiveData{$basename} if defined $archiveData->{$basename}{courseID}; next; } - $archiveDataUpdated = 1; - $archiveData->{$basename} = { lastModified => $lastModified }; + $updatedArchiveData{$basename} = { lastModified => $lastModified }; my $archive = Archive::Tar->new($_); my %top_level; @@ -164,20 +161,12 @@ sub listArchivedCourses { } my ($currCourseID) = keys %top_level; - $archiveData->{$basename}{courseID} = $currCourseID; - $archiveData->{$basename}{size} = getHumanReadableFileSize($_); - $return{$currCourseID} = { filename => $basename, size => $archiveData->{$basename}{size} }; - } - - my %archives = map { $_->basename => 1 } @$archives; - for (keys %$archiveData) { - unless ($archives{$_}) { - delete $archiveData->{$_}; - $archiveDataUpdated = 1; - } + $updatedArchiveData{$basename}{courseID} = $currCourseID; + $updatedArchiveData{$basename}{size} = getHumanReadableFileSize($_); + $return{$basename} = $updatedArchiveData{$basename}; } - $archiveDataFile->spew(encode_json($archiveData)) if $archiveDataUpdated; + $archiveDataFile->spew(encode_json(\%updatedArchiveData)); return %return; } diff --git a/templates/ContentGenerator/CourseAdmin.html.ep b/templates/ContentGenerator/CourseAdmin.html.ep index 793a193ac0..9b0c224115 100644 --- a/templates/ContentGenerator/CourseAdmin.html.ep +++ b/templates/ContentGenerator/CourseAdmin.html.ep @@ -67,7 +67,7 @@
    % my %courseArchives = listArchivedCourses($ce); % for (sort { lc($a) cmp lc($b) } keys %courseArchives) { -
  1. <%= "$_ ($courseArchives{$_}{size})" %>
  2. +
  3. <%= "$courseArchives{$_}{courseID} ($_, $courseArchives{$_}{size})" %>
  4. % }
% } diff --git a/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep index d1e8082fb2..d2b47162c4 100644 --- a/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep @@ -4,9 +4,9 @@ % % # Find courses which have been archived. % my %courseArchives = listArchivedCourses($ce); -% my @courseIDs = sort { lc($a) cmp lc($b) } keys %courseArchives; +% my @archiveFiles = sort { lc($a) cmp lc($b) } keys %courseArchives; % -% if (@courseIDs) { +% if (@archiveFiles) { <%= form_for current_route, method => 'POST', begin =%> <%= $c->hidden_authen_fields =%> <%= $c->hidden_fields('subDisplay') =%> @@ -19,9 +19,9 @@ class => 'col-md-2 pe-0 col-form-label fw-bold' =%>
<%= select_field - unarchive_courseID => [ - map { [ "$_ ($courseArchives{$_}{size})" => $courseArchives{$_}{filename} ] } @courseIDs - ], + unarchive_courseID => + [ map { [ "$courseArchives{$_}{courseID} ($_, $courseArchives{$_}{size})" => $_ ] } + @archiveFiles ], id => 'unarchive_courseID', class => 'form-select', size => 10 From 648d55688d42ab9d8df5fdd24d2d351ac47f25a4 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 5 Aug 2026 18:00:54 -0500 Subject: [PATCH 2/2] Only show the archive file name for archive files that contain duplicate course ids. --- lib/WeBWorK/Utils/CourseManagement.pm | 22 +++++++++++++++++-- .../ContentGenerator/CourseAdmin.html.ep | 6 ++++- .../CourseAdmin/unarchive_course_form.html.ep | 8 ++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 4563b0514a..cfcc05f62e 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -123,6 +123,9 @@ containing the C, file C, and C time. For example, } } +Note that archive files that contain the same C will also have the +C key set to 1. + =cut sub listArchivedCourses { @@ -135,15 +138,30 @@ sub listArchivedCourses { my $archiveDataFile = $archivesDir->child('archive-cache.json'); my $archiveData = eval { decode_json($archiveDataFile->slurp) } || {}; + my %seenCourseIDs; my %updatedArchiveData; my %return; + + my $setReturnData = sub { + my $basename = shift; + $return{$basename} = { %{ $updatedArchiveData{$basename} } }; + + if ($seenCourseIDs{ $updatedArchiveData{$basename}{courseID} }) { + $return{$basename}{duplicateCourseID} = 1; + $return{ $seenCourseIDs{ $updatedArchiveData{$basename}{courseID} } }{duplicateCourseID} = 1; + } else { + $seenCourseIDs{ $updatedArchiveData{$basename}{courseID} } = $basename; + } + return; + }; + for (@$archives) { my $basename = $_->basename; my $lastModified = $_->stat->mtime; if ($archiveData->{$basename} && $archiveData->{$basename}{lastModified} >= $lastModified) { $updatedArchiveData{$basename} = $archiveData->{$basename}; - $return{$basename} = $updatedArchiveData{$basename} if defined $archiveData->{$basename}{courseID}; + $setReturnData->($basename) if defined $archiveData->{$basename}{courseID}; next; } @@ -163,7 +181,7 @@ sub listArchivedCourses { $updatedArchiveData{$basename}{courseID} = $currCourseID; $updatedArchiveData{$basename}{size} = getHumanReadableFileSize($_); - $return{$basename} = $updatedArchiveData{$basename}; + $setReturnData->($basename); } $archiveDataFile->spew(encode_json(\%updatedArchiveData)); diff --git a/templates/ContentGenerator/CourseAdmin.html.ep b/templates/ContentGenerator/CourseAdmin.html.ep index 9b0c224115..7e6d262cef 100644 --- a/templates/ContentGenerator/CourseAdmin.html.ep +++ b/templates/ContentGenerator/CourseAdmin.html.ep @@ -67,7 +67,11 @@
    % my %courseArchives = listArchivedCourses($ce); % for (sort { lc($a) cmp lc($b) } keys %courseArchives) { -
  1. <%= "$courseArchives{$_}{courseID} ($_, $courseArchives{$_}{size})" %>
  2. +
  3. + <%= "$courseArchives{$_}{courseID} (" + . ($courseArchives{$_}{duplicateCourseID} ? "$_, " : "") + . "$courseArchives{$_}{size})" %> +
  4. % }
% } diff --git a/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep index d2b47162c4..eca00efdf9 100644 --- a/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep @@ -19,9 +19,11 @@ class => 'col-md-2 pe-0 col-form-label fw-bold' =%>
<%= select_field - unarchive_courseID => - [ map { [ "$courseArchives{$_}{courseID} ($_, $courseArchives{$_}{size})" => $_ ] } - @archiveFiles ], + unarchive_courseID => [ + map { [ "$courseArchives{$_}{courseID} (" + . ($courseArchives{$_}{duplicateCourseID} ? "$_, " : "") + . "$courseArchives{$_}{size})" => $_ ] } @archiveFiles + ], id => 'unarchive_courseID', class => 'form-select', size => 10