Skip to content
Snippets Groups Projects
Commit f1704f9a authored by fkiefer's avatar fkiefer
Browse files

OO-2455 Download of all participant folders in course archive tool

parent 090a35d3
No related branches found
No related tags found
No related merge requests found
......@@ -52,6 +52,8 @@ import org.olat.course.nodes.CheckListCourseNode;
import org.olat.course.nodes.DialogCourseNode;
import org.olat.course.nodes.FOCourseNode;
import org.olat.course.nodes.GTACourseNode;
import org.olat.course.nodes.IQTESTCourseNode;
import org.olat.course.nodes.PFCourseNode;
import org.olat.course.nodes.ProjectBrokerCourseNode;
import org.olat.course.nodes.ScormCourseNode;
import org.olat.course.nodes.TACourseNode;
......@@ -78,6 +80,8 @@ public class ArchiverMainController extends MainLayoutBasicController {
private static final String CMD_WIKIS = "wikis";
private static final String CMD_SCORM = "scorm";
private static final String CMD_CHECKLIST = "checklist";
private static final String CMD_PARTICIPANTFOLDER = "participantfolder";
private IArchiverCallback archiverCallback;
......@@ -228,6 +232,13 @@ public class ArchiverMainController extends MainLayoutBasicController {
gtn.setAltText(translate("menu.dialogs.alt"));
root.addChild(gtn);
}
if (archiverCallback.mayArchiveParticipantFolder()) {
gtn = new GenericTreeNode();
gtn.setTitle(translate("menu.participantfolder"));
gtn.setUserObject(CMD_PARTICIPANTFOLDER);
gtn.setAltText("menu.participantfolder.alt");
root.addChild(gtn);
}
if (archiverCallback.mayArchiveWikis()) {
gtn = new GenericTreeNode();
gtn.setTitle(translate("menu.wikis"));
......@@ -250,6 +261,7 @@ public class ArchiverMainController extends MainLayoutBasicController {
root.addChild(gtn);
}
//add extension menues
ExtManager extm = ExtManager.getInstance();
Class extensionPointMenu = this.getClass();
......@@ -284,7 +296,7 @@ public class ArchiverMainController extends MainLayoutBasicController {
contentCtr = new CourseQTIArchiveController(ureq, getWindowControl(), ores);
main.setContent(contentCtr.getInitialComponent());
} else if (menuCommand.equals(CMD_SCOREACCOUNTING)) {
contentCtr = new ScoreAccountingArchiveController(ureq, getWindowControl(), ores);
contentCtr = new ScoreAccountingArchiveController(ureq, getWindowControl(), ores, new IQTESTCourseNode());
main.setContent(contentCtr.getInitialComponent());
} else if (menuCommand.equals(CMD_ARCHIVELOGFILES)) {
contentCtr = new CourseLogsArchiveController(ureq, getWindowControl(), ores);
......@@ -313,7 +325,11 @@ public class ArchiverMainController extends MainLayoutBasicController {
} else if (menuCommand.equals(CMD_CHECKLIST)) {
contentCtr = new GenericArchiveController(ureq, getWindowControl(), ores, new CheckListCourseNode());
main.setContent(contentCtr.getInitialComponent());
} else if (menuCommand.equals(CMD_PARTICIPANTFOLDER)) {
contentCtr = new GenericArchiveController(ureq, getWindowControl(), ores, new PFCourseNode());
main.setContent(contentCtr.getInitialComponent());
}
listenTo(contentCtr);
}
}
......
......@@ -76,6 +76,11 @@ public class FullAccessArchiverCallback implements IArchiverCallback {
public boolean mayArchiveChecklist() {
return true;
}
@Override
public boolean mayArchiveParticipantFolder() {
return true;
}
@Override
public boolean mayArchiveProjectBroker() {
......
......@@ -69,6 +69,8 @@ public interface IArchiverCallback {
public boolean mayArchiveChecklist();
public boolean mayArchiveParticipantFolder();
/**
* @return true if user has rights to archive project-broker data
*/
......
......@@ -33,6 +33,8 @@ course.res.feedback=Die Datei <b>{0}</b> mit den Resultaten liegt in Ihrem pers\
course.res.intro=Klicken Sie den untenstehenden 'Start'-Knopf um Endresultate von Tests, Bewertungen und Aufgaben zu archivieren.
course.res.title=Kursresultate
dialog=Dateidiskussionen
iqtest=Testergebnisse
pf=Teilnehmer Ordner
fo=Forums
gta=Aufgaben und Gruppenaufgaben
index.intro=Mit diesem Werkzeug k\u00F6nnen verschiedene Daten dieses OLAT-Kurses archiviert werden.<p> W\u00E4hlen Sie links im Menu einen Eintrag aus um zu beginnen.
......@@ -50,6 +52,8 @@ menu.archivelogfiles=Logfiles
menu.archivelogfiles.alt=Administrator-, Kurs- und Userlogfiles
menu.checklist=Checklisten
menu.checklist.alt=Checklisten
menu.participantfolder=Teilnehmer Ordner
menu.participantfolder.alt=Teilnehmer Ordner
menu.dialogs=Dateidiskussionen
menu.dialogs.alt=Dateidiskussionen archivieren
menu.forums=Foren
......@@ -70,7 +74,9 @@ menu.scorm=SCORM Resultate
menu.scorm.alt=SCORM Resultate
menu.wikis=Wikis
menu.wikis.alt=Wikis archivieren
nodechoose.intro.pf=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Ordnerinhalte zu archivieren.
nodechoose.intro.cl=W\u00E4hlen Sie im folgenden Dialog den Checklisten Baustein aus, der ausgewertet werden soll
nodechoose.intro.iqtest=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Testergebnisse zu archivieren.
nodechoose.intro.dialog=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Dateidiskussion zu archivieren.
nodechoose.intro.fo=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Forum zu archivieren.
nodechoose.intro.gta=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Gruppenaufgaben zu archivieren.
......@@ -79,9 +85,11 @@ nodechoose.intro.scorm=W\u00E4hlen Sie im folgenden Dialog den SCORM Baustein au
nodechoose.intro.ta=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Aufgaben/L\u00F6sungen zu archivieren.
nodechoose.intro.wiki=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Wikis zu archivieren.
overview.nonodes.checklist=Dieser Kurs enth\u00E4lt keine Checkliste.
overview.nonodes.iqtest=No QTI
overview.nonodes.dialog=Dieser Kurs enth\u00E4lt keine Dateidiskussionen.
overview.nonodes.fo=Dieser Kurs enth\u00E4lt keine Foren
overview.nonodes.gta=Dieser Kurs enth\u00E4lt keine Aufgabenkursbausteine.
overview.nonodes.pf=Dieser Kurs enth\u00E4lt keine Teilnehmer Ordner.
overview.nonodes.projectbroker=Dieser Kurs enth\u00E4lt keinen Baustein "Themenvergabe".
overview.nonodes.scorm=Dieser Kurs hat keinen SCORM Baustein.
overview.nonodes.ta=Dieser Kurs enth\u00E4lt keine Aufgabenkursbausteine
......
......@@ -32,7 +32,9 @@ course.logs.title=Course log data
course.res.feedback=You will find the file <b>{0}</b> along with some results in your personal folder of the private/archive/ section.
course.res.intro=Click the 'Start' button below in order to archive final results from tests, assessments, and tasks.
course.res.title=Course results
pf=Participant Folder
dialog=File dialogs
iqtest=Test results
fo=Forums
gta=Tasks and group tasks
index.intro=By means of this tool you can archive various data from your OLAT course.<p> Choose a topic from the menu on the left to start.
......@@ -50,6 +52,8 @@ menu.archivelogfiles=Log files
menu.archivelogfiles.alt=Administrator, course, and user log files
menu.checklist=Checklists
menu.checklist.alt=Checklists
menu.participantfolder=Participant Folder
menu.participantfolder.alt=Participant Folder
menu.dialogs=File dialogs
menu.dialogs.alt=Archive file dialogs
menu.forums=Forums
......@@ -70,7 +74,9 @@ menu.scorm=SCORM results
menu.scorm.alt=SCORM results
menu.wikis=Wikis
menu.wikis.alt=Archive Wikis
nodechoose.intro.pf=Choose a course element to archive the content of the participant folder.
nodechoose.intro.cl=Please select the checklist element you wish to archive.
nodechoose.intro.iqtest=Select a course element to archive its test results.
nodechoose.intro.dialog=Select a course element to archive its file dialog.
nodechoose.intro.fo=Select a course element to archive its forum.
nodechoose.intro.gta=Choose a course element to archive the group tasks.
......@@ -82,6 +88,7 @@ overview.nonodes.checklist=This course does not contain any checklists.
overview.nonodes.dialog=This course does not contain any file dialogs.
overview.nonodes.fo=This course does not contain any forums.
overview.nonodes.gta=This course does not contain any task elements.
overview.nonodes.pf=This course does not contain any participant folders.
overview.nonodes.projectbroker=This course does not contain any topic assignments.
overview.nonodes.scorm=This course does not contain any SCORM element.
overview.nonodes.ta=This course does not contain any task elements.
......
......@@ -23,6 +23,8 @@ import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.Locale;
import java.util.zip.ZipOutputStream;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
......@@ -33,6 +35,7 @@ import org.olat.core.gui.components.stack.BreadcrumbPanel;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.util.FileUtils;
import org.olat.core.util.Util;
......@@ -43,6 +46,7 @@ import org.olat.course.ICourse;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.editor.NodeEditController;
import org.olat.course.editor.StatusDescription;
import org.olat.course.nodes.pf.manager.FileSystemExport;
import org.olat.course.nodes.pf.manager.PFManager;
import org.olat.course.nodes.pf.ui.PFEditController;
import org.olat.course.nodes.pf.ui.PFPeekviewController;
......@@ -275,6 +279,16 @@ public class PFCourseNode extends AbstractAccessableCourseNode {
FileUtils.deleteDirsAndFiles(root, true, true);
}
}
@Override
public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream,
String charset) {
CourseEnvironment courseEnv = course.getCourseEnvironment();
Path sourceFolder = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(),
PFManager.FILENAME_PARTICIPANTFOLDER, getIdent());
Translator translator = Util.createPackageTranslator(PFRunController.class, locale);
return FileSystemExport.fsToZip(exportStream, sourceFolder, this, null, translator);
}
@Override
public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
......
......@@ -19,6 +19,7 @@
*/
package org.olat.course.nodes.pf.manager;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
......@@ -42,6 +43,7 @@ import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.course.nodes.PFCourseNode;
import org.olat.course.nodes.pf.ui.PFRunController;
......@@ -101,14 +103,10 @@ public class FileSystemExport implements MediaResource {
try (ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) {
zout.setLevel(9);
String pfolder = translator.translate("participant.folder") + "/";
Path relPath = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(),
PFManager.FILENAME_PARTICIPANTFOLDER, pfNode.getIdent());
fsToZip(zout, relPath, pfolder);
zout.close();
fsToZip(zout, relPath, pfNode, identities, translator);
} catch (IOException e) {
log.error("", e);
......@@ -120,49 +118,84 @@ public class FileSystemExport implements MediaResource {
//
}
private void fsToZip(ZipOutputStream zout, final Path sourceFolder, final String targetPath) throws IOException {
/**
* Exports a given filesystem as Zip-Outputstream
*
* @param zout the Zip-Outputstream
* @param sourceFolder the source folder
* @param pfNode the PFCourseNode
* @param identities
* @param translator
* @throws IOException Signals that an I/O exception has occurred.
*/
public static boolean fsToZip(ZipOutputStream zout, final Path sourceFolder, PFCourseNode pfNode,
List<Identity> identities, Translator translator) {
String targetPath = translator.translate("participant.folder") + "/";
UserManager userManager = CoreSpringFactory.getImpl(UserManager.class);
Set<Long> idKeys = new HashSet<>();
for (Identity identity : identities) {
idKeys.add(identity.getKey());
Set<String> idKeys = new HashSet<>();
if (identities != null) {
for (Identity identity : identities) {
idKeys.add(identity.getKey().toString());
}
} else {
for (File file : sourceFolder.toFile().listFiles()) {
if (file.isDirectory()) {
idKeys.add(file.getName());
}
}
}
Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() {
private String containsID (String relPath) {
for (Long key : idKeys) {
if (relPath.contains(key.toString())) {
String exportFolderName = userManager.getUserDisplayName(key).replace(", ", "_") + "_" + key;
return relPath.replace(key.toString(), exportFolderName);
try {
Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() {
//contains identity check and changes identity key to user display name
private String containsID (String relPath) {
for (String key : idKeys) {
//additional check if folder is a identity-key (coming from fs)
if (relPath.contains(key) && StringHelper.isLong(key)) {
String exportFolderName = userManager.getUserDisplayName(Long.parseLong(key)).replace(", ", "_")
+ "_" + key;
return relPath.replace(key.toString(), exportFolderName);
}
}
return null;
}
return null;
}
private boolean boxesEnabled(String relPath) {
return pfNode.hasParticipantBoxConfigured() && relPath.contains(PFManager.FILENAME_DROPBOX)
|| pfNode.hasCoachBoxConfigured() && relPath.contains(PFManager.FILENAME_RETURNBOX);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String relPath = sourceFolder.relativize(file).toString();
if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) {
zout.putNextEntry(new ZipEntry(targetPath + relPath));
Files.copy(file, zout);
zout.closeEntry();
//checks module config and translates folder name
private String boxesEnabled(String relPath) {
if (pfNode.hasParticipantBoxConfigured() && relPath.contains(PFManager.FILENAME_DROPBOX)) {
return relPath.replace(PFManager.FILENAME_DROPBOX, translator.translate("drop.box"));
} else if (pfNode.hasCoachBoxConfigured() && relPath.contains(PFManager.FILENAME_RETURNBOX)) {
return relPath.replace(PFManager.FILENAME_RETURNBOX, translator.translate("return.box"));
} else {
return null;
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String relPath = sourceFolder.relativize(file).toString();
if ((relPath = containsID(relPath)) != null && (relPath = boxesEnabled(relPath)) != null) {
zout.putNextEntry(new ZipEntry(targetPath + relPath));
Files.copy(file, zout);
zout.closeEntry();
}
return FileVisitResult.CONTINUE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String relPath = sourceFolder.relativize(dir).toString() + "/";
if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) {
zout.putNextEntry(new ZipEntry(targetPath + relPath));
zout.closeEntry();
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String relPath = sourceFolder.relativize(dir).toString() + "/";
if ((relPath = containsID(relPath)) != null && (relPath = boxesEnabled(relPath)) != null) {
zout.putNextEntry(new ZipEntry(targetPath + relPath));
zout.closeEntry();
}
return FileVisitResult.CONTINUE;
}
return FileVisitResult.CONTINUE;
}
});
});
zout.close();
return true;
} catch (IOException e) {
log.error("Unable to export zip",e);
return false;
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment