From 6f6aa10c5568b0179a56558be56809e7182fecb7 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 | 7 ++-- 3 files changed, 20 insertions(+), 30 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 4b56860c53..88dfdf7d8a 100644 --- a/templates/ContentGenerator/CourseAdmin.html.ep +++ b/templates/ContentGenerator/CourseAdmin.html.ep @@ -65,7 +65,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 b42250fbd1..1746d22985 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') =%> @@ -21,7 +21,8 @@
<%= select_field unarchive_courseID => - [ map { [ "$_ ($courseArchives{$_}{size})" => $courseArchives{$_}{filename} ] } @courseIDs ], + [ map { [ "$courseArchives{$_}{courseID} ($_, $courseArchives{$_}{size})" => $_ ] } + @archiveFiles ], id => 'unarchive_courseID', class => 'form-select', size => 10 =%> From 12c556aed095ea7ae63f67e4031b080833482a2b 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 88dfdf7d8a..631400d5a9 100644 --- a/templates/ContentGenerator/CourseAdmin.html.ep +++ b/templates/ContentGenerator/CourseAdmin.html.ep @@ -65,7 +65,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 1746d22985..1c0610acc7 100644 --- a/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/unarchive_course_form.html.ep @@ -20,9 +20,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 =%>