From f3c227e1784691a5c9b91ea8380a43aba96e7f52 Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Wed, 18 Sep 2019 12:33:54 +0200 Subject: [PATCH] OO-4255: streams the course export to the browser without copy of course --- src/main/java/org/olat/core/util/ZipUtil.java | 38 ++ .../java/org/olat/course/CourseFactory.java | 32 -- src/main/java/org/olat/course/ICourse.java | 11 - .../org/olat/course/PersistingCourseImpl.java | 152 +------ .../export/CourseExportMediaResource.java | 427 ++++++++++++++++++ .../groupsandrights/CourseGroupManager.java | 2 +- .../PersistingCourseGroupManager.java | 4 +- .../preview/PreviewCourseGroupManager.java | 2 +- .../handler/BinderTemplateMediaResource.java | 2 +- .../modules/reminder/ReminderService.java | 3 +- .../reminder/manager/ReminderServiceImpl.java | 23 +- .../RepositoryEntryImportExport.java | 98 ++-- .../repository/handlers/CourseHandler.java | 11 +- 13 files changed, 549 insertions(+), 256 deletions(-) create mode 100644 src/main/java/org/olat/course/export/CourseExportMediaResource.java diff --git a/src/main/java/org/olat/core/util/ZipUtil.java b/src/main/java/org/olat/core/util/ZipUtil.java index e09036d903d..68ee5d8b049 100644 --- a/src/main/java/org/olat/core/util/ZipUtil.java +++ b/src/main/java/org/olat/core/util/ZipUtil.java @@ -835,6 +835,44 @@ public class ZipUtil { } } + /** + * Add a directory to a zip stream. The files path are relative to the + * specified directory. The name of the directory is not part of + * the path of its files. + * + * @param path The path + * @param directory The directory to zip + * @param exportStream The stream + */ + public static void addPathToZip(final String path, final Path directory, final ZipOutputStream exportStream) { + try { + Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if(!attrs.isDirectory()) { + Path relativeFile = directory.relativize(file); + String name = relativeFile.toString(); + if(StringHelper.containsNonWhitespace(path)) { + name = path + "/" + name; + } + exportStream.putNextEntry(new ZipEntry(name)); + + try(InputStream in=Files.newInputStream(file)) { + FileUtils.cpio(in, exportStream, ""); + } catch (Exception e) { + log.error("", e); + } + + exportStream.closeEntry(); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.error("", e); + } + } + /** * Zip all files under a certain root directory. (with compression) * diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index fa7a5685a51..791531add00 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -32,11 +32,9 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.zip.ZipOutputStream; @@ -73,7 +71,6 @@ import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.Tracing; -import org.olat.core.util.CodeHelper; import org.olat.core.util.ExportUtil; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; @@ -81,7 +78,6 @@ import org.olat.core.util.ObjectCloner; import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.Util; -import org.olat.core.util.WebappHelper; import org.olat.core.util.ZipUtil; import org.olat.core.util.cache.CacheWrapper; import org.olat.core.util.coordinate.CoordinatorManager; @@ -504,34 +500,6 @@ public class CourseFactory { return targetRes; } - /** - * Exports an entire course to a zip file. - * - * @param sourceRes - * @param fTargetZIP - * @return true if successfully exported, false otherwise. - */ - public static void exportCourseToZIP(OLATResourceable sourceRes, File fTargetZIP, boolean runtimeDatas) { - PersistingCourseImpl sourceCourse = (PersistingCourseImpl) loadCourse(sourceRes); - - // add files to ZIP - File fExportDir = new File(WebappHelper.getTmpDir(), CodeHelper.getUniqueID()); - fExportDir.mkdirs(); - log.info("Export folder: " + fExportDir); - synchronized (sourceCourse) { //o_clusterNOK - cannot be solved with doInSync since could take too long (leads to error: "Lock wait timeout exceeded") - OLATResource courseResource = sourceCourse.getCourseEnvironment().getCourseGroupManager().getCourseResource(); - sourceCourse.exportToFilesystem(courseResource, fExportDir, runtimeDatas); - Set<String> fileSet = new HashSet<>(); - String[] files = fExportDir.list(); - for (int i = 0; i < files.length; i++) { - fileSet.add(files[i]); - } - ZipUtil.zip(fileSet, fExportDir, fTargetZIP, false); - log.info("Delete export folder: " + fExportDir); - FileUtils.deleteDirsAndFiles(fExportDir, true, true); - } - } - /** * Import a course from a ZIP file. * diff --git a/src/main/java/org/olat/course/ICourse.java b/src/main/java/org/olat/course/ICourse.java index 1c6a875588b..10e1ee71fd5 100644 --- a/src/main/java/org/olat/course/ICourse.java +++ b/src/main/java/org/olat/course/ICourse.java @@ -36,7 +36,6 @@ import org.olat.course.export.CourseEnvironmentMapper; import org.olat.course.folder.CourseContainerOptions; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.tree.CourseEditorTreeModel; -import org.olat.resource.OLATResource; /** * Description:<BR/> @@ -62,16 +61,6 @@ public interface ICourse extends OLATResourceable { * @return The course editor tree model for this course */ public CourseEditorTreeModel getEditorTreeModel(); - - /** - * Export course to file system. - * @param originalCourseResource The original resource - * @param exportDirectory The directory to export files to. - * @param runtimeDatas Export with runtime datas (true add archives of the groups...) - * @param backwardsCompatible Export in a format compatible with older OpenOLAT version - * @param foldersToCleanup Can add there folders which need to be clean up after the export - */ - public void exportToFilesystem(OLATResource originalCourseResource, File exportDirectory, boolean runtimeDatas); public void postCopy(CourseEnvironmentMapper envMapper, ICourse sourceCourse); diff --git a/src/main/java/org/olat/course/PersistingCourseImpl.java b/src/main/java/org/olat/course/PersistingCourseImpl.java index 0be14b62e57..bcf9696b9e6 100644 --- a/src/main/java/org/olat/course/PersistingCourseImpl.java +++ b/src/main/java/org/olat/course/PersistingCourseImpl.java @@ -29,20 +29,16 @@ import java.io.File; import java.io.InputStream; import java.io.Serializable; +import org.apache.logging.log4j.Logger; import org.olat.admin.quota.QuotaConstants; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.vfs.VFSRepositoryService; import org.olat.core.id.IdentityEnvironment; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; -import org.olat.core.util.FileUtils; -import org.olat.core.util.ZipUtil; import org.olat.core.util.nodes.INode; -import org.olat.core.util.tree.TreeVisitor; import org.olat.core.util.tree.Visitor; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.VFSContainer; @@ -63,11 +59,7 @@ import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.environment.CourseEnvironmentImpl; import org.olat.course.tree.CourseEditorTreeModel; import org.olat.course.tree.CourseEditorTreeNode; -import org.olat.modules.glossary.GlossaryManager; -import org.olat.modules.reminder.ReminderService; -import org.olat.modules.sharedfolder.SharedFolderManager; import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryEntryImportExport; import org.olat.repository.RepositoryManager; import org.olat.resource.OLATResource; @@ -90,9 +82,9 @@ public class PersistingCourseImpl implements ICourse, OLATResourceable, Serializ public static final String COURSE_ROOT_DIR_NAME = "course"; - private static final String EDITORTREEMODEL_XML = "editortreemodel.xml"; - private static final String RUNSTRUCTURE_XML = "runstructure.xml"; - private static final String ORES_TYPE_NAME = CourseModule.getCourseTypeName(); + public static final String EDITORTREEMODEL_XML = "editortreemodel.xml"; + public static final String RUNSTRUCTURE_XML = "runstructure.xml"; + public static final String ORES_TYPE_NAME = CourseModule.getCourseTypeName(); public static final String COURSEFOLDER = "coursefolder"; private Long resourceableId; @@ -260,7 +252,7 @@ public class PersistingCourseImpl implements ICourse, OLATResourceable, Serializ /** * @return The directory "coursefolder" or storage folder of the course */ - protected File getIsolatedCourseBaseFolder() { + public File getIsolatedCourseBaseFolder() { // create local course folder return VFSManager.olatRootDirectory(courseRootContainer.getRelPath() + File.separator + COURSEFOLDER); } @@ -268,7 +260,7 @@ public class PersistingCourseImpl implements ICourse, OLATResourceable, Serializ /** * @return The directory "coursefolder" or storage folder of the course */ - protected VFSContainer getIsolatedCourseBaseContainer() { + public VFSContainer getIsolatedCourseBaseContainer() { // create local course folder return VFSManager.olatRootContainer(courseRootContainer.getRelPath() + File.separator + COURSEFOLDER, null); } @@ -288,107 +280,6 @@ public class PersistingCourseImpl implements ICourse, OLATResourceable, Serializ writeObject(EDITORTREEMODEL_XML, getEditorTreeModel()); log.debug("saveEditorTreeModel"); } - - /** - * @see org.olat.course.ICourse#exportToFilesystem(java.io.File) - * <p> - * See OLAT-5368: Course Export can take longer than say 2min. - * <p> - */ - @Override - public void exportToFilesystem(OLATResource originalCourseResource, File exportDirectory, boolean runtimeDatas) { - long s = System.currentTimeMillis(); - log.info("exportToFilesystem: exporting course "+this+" to "+exportDirectory+"..."); - File fCourseBase = getCourseBaseContainer().getBasefile(); - //make the folder structure - File fExportedDataDir = new File(exportDirectory, EXPORTED_DATA_FOLDERNAME); - fExportedDataDir.mkdirs(); - - //export course config - FileUtils.copyFileToDir(new File(fCourseBase, CourseConfigManager.COURSECONFIG_XML), exportDirectory, "course export courseconfig"); - - //export business groups - CourseEnvironmentMapper envMapper = getCourseEnvironment().getCourseGroupManager().getBusinessGroupEnvironment(); - - getCourseEnvironment().getCourseGroupManager().exportCourseBusinessGroups(fExportedDataDir, envMapper, runtimeDatas); - // export editor structure - FileUtils.copyFileToDir(new File(fCourseBase, EDITORTREEMODEL_XML), exportDirectory, "course export exitortreemodel"); - // export run structure - FileUtils.copyFileToDir(new File(fCourseBase, RUNSTRUCTURE_XML), exportDirectory, "course export runstructure"); - - // export layout and media folder - FileUtils.copyDirToDir(new File(fCourseBase, "layout"), exportDirectory, "course export layout folder"); - FileUtils.copyDirToDir(new File(fCourseBase, "media"), exportDirectory, "course export media folder"); - // export course folder - File fExportedCoursefolderZip = new File(exportDirectory, "oocoursefolder.zip"); - File courseFolder = getIsolatedCourseBaseFolder(); - ZipUtil.zipAll(courseFolder, fExportedCoursefolderZip); - - // export any node data - log.info("exportToFilesystem: exporting course "+this+": exporting all nodes..."); - Visitor visitor = new NodeExportVisitor(fExportedDataDir, this); - TreeVisitor tv = new TreeVisitor(visitor, getEditorTreeModel().getRootNode(), true); - tv.visitAll(); - log.info("exportToFilesystem: exporting course "+this+": exporting all nodes...done."); - - // Do intermediate commit to avoid transaction timeout - DBFactory.getInstance().intermediateCommit(); - - // export shared folder - CourseConfig config = getCourseConfig(); - if (config.hasCustomSharedFolder()) { - log.info("exportToFilesystem: exporting course "+this+": shared folder..."); - if (!SharedFolderManager.getInstance().exportSharedFolder( - config.getSharedFolderSoftkey(), fExportedDataDir)) { - // export failed, delete reference to shared folder in the course config - log.info("exportToFilesystem: exporting course "+this+": export of shared folder failed."); - config.setSharedFolderSoftkey(CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); - CoreSpringFactory.getImpl(CourseConfigManager.class).saveConfigTo(this, config); - } - log.info("exportToFilesystem: exporting course "+this+": shared folder...done."); - } - - // Do intermediate commit to avoid transaction timeout - DBFactory.getInstance().intermediateCommit(); - - // export glossary - if (config.hasGlossary()) { - log.info("exportToFilesystem: exporting course "+this+": glossary..."); - final GlossaryManager glossaryManager = CoreSpringFactory.getImpl(GlossaryManager.class); - final CourseConfigManager courseConfigManager = CoreSpringFactory.getImpl(CourseConfigManager.class); - if (!glossaryManager.exportGlossary( - config.getGlossarySoftKey(), fExportedDataDir)) { - // export failed, delete reference to glossary in the course config - log.info("exportToFilesystem: exporting course "+this+": export of glossary failed."); - config.setGlossarySoftKey(null); - courseConfigManager.saveConfigTo(this, config); - } - log.info("exportToFilesystem: exporting course "+this+": glossary...done."); - } - - // Do intermediate commit to avoid transaction timeout - DBFactory.getInstance().intermediateCommit(); - - log.info("exportToFilesystem: exporting course "+this+": configuration and repo data..."); - // export configuration file - FileUtils.copyFileToDir(new File(fCourseBase, CourseConfigManager.COURSECONFIG_XML), exportDirectory, "course export configuration and repo info"); - - // export repo metadata - RepositoryManager rm = RepositoryManager.getInstance(); - RepositoryEntry myRE = rm.lookupRepositoryEntry(this, true); - RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(myRE, fExportedDataDir); - importExport.exportDoExportProperties(); - - // Do intermediate commit to avoid transaction timeout - DBFactory.getInstance().intermediateCommit(); - - //export reminders - CoreSpringFactory.getImpl(ReminderService.class) - .exportReminders(myRE, fExportedDataDir); - - log.info("exportToFilesystem: exporting course "+this+" to "+exportDirectory+" done."); - log.info("finished export course '"+getCourseTitle()+"' in t="+Long.toString(System.currentTimeMillis()-s)); - } @Override public void postCopy(CourseEnvironmentMapper envMapper, ICourse sourceCourse) { @@ -614,34 +505,3 @@ class NodePostCopyVisitor implements Visitor { } } } - -class NodeExportVisitor implements Visitor { - - private File exportDirectory; - private ICourse course; - - /** - * Constructor of the node deletion visitor - * - * @param exportDirectory - * @param course - */ - public NodeExportVisitor(File exportDirectory, ICourse course) { - this.exportDirectory = exportDirectory; - this.course = course; - } - - /** - * Visitor pattern to delete the course nodes - * - * @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode) - */ - @Override - public void visit(INode node) { - CourseEditorTreeNode cNode = (CourseEditorTreeNode) node; - cNode.getCourseNode().exportNode(exportDirectory, course); - // Do frequent intermediate commits to avoid transaction timeout - DBFactory.getInstance().intermediateCommit(); - } - -} diff --git a/src/main/java/org/olat/course/export/CourseExportMediaResource.java b/src/main/java/org/olat/course/export/CourseExportMediaResource.java new file mode 100644 index 00000000000..88b13d58d4c --- /dev/null +++ b/src/main/java/org/olat/course/export/CourseExportMediaResource.java @@ -0,0 +1,427 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.export; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.Logger; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.ServletUtil; +import org.olat.core.id.OLATResourceable; +import org.olat.core.logging.Tracing; +import org.olat.core.util.CodeHelper; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; +import org.olat.core.util.WebappHelper; +import org.olat.core.util.ZipUtil; +import org.olat.core.util.io.ShieldOutputStream; +import org.olat.core.util.io.SystemFileFilter; +import org.olat.core.util.nodes.INode; +import org.olat.core.util.tree.TreeVisitor; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSManager; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.PersistingCourseImpl; +import org.olat.course.config.CourseConfig; +import org.olat.course.config.CourseConfigManager; +import org.olat.course.nodes.BCCourseNode; +import org.olat.course.nodes.CPCourseNode; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.ScormCourseNode; +import org.olat.course.nodes.VideoCourseNode; +import org.olat.course.nodes.cp.CPEditController; +import org.olat.course.nodes.video.VideoEditController; +import org.olat.course.tree.CourseEditorTreeNode; +import org.olat.modules.glossary.GlossaryManager; +import org.olat.modules.reminder.ReminderService; +import org.olat.modules.sharedfolder.SharedFolderManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryImportExport; +import org.olat.repository.RepositoryManager; +import org.olat.resource.OLATResource; + +/** + * + * Initial date: 17 sept. 2019<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CourseExportMediaResource implements MediaResource { + + private static Logger log = Tracing.createLoggerFor(CourseExportMediaResource.class); + + private final OLATResourceable resource; + + public CourseExportMediaResource(OLATResourceable resource) { + this.resource = resource; + } + + @Override + public boolean acceptRanges() { + return false; + } + + @Override + public long getCacheControlDuration() { + return ServletUtil.CACHE_NO_CACHE; + } + + @Override + public String getContentType() { + return "application/zip"; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public InputStream getInputStream() { + return null; + } + + @Override + public Long getLastModified() { + return null; + } + + @Override + public void prepare(HttpServletResponse hres) { + try { + hres.setCharacterEncoding("UTF-8"); + } catch (Exception e) { + log.error("", e); + } + + try(ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) { + RepositoryEntry entry = RepositoryManager.getInstance().lookupRepositoryEntry(resource, true); + String label = StringHelper.transformDisplayNameToFileSystemName(entry.getDisplayname()); + hres.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + StringHelper.urlEncodeUTF8(label + ".zip")); + hres.setHeader("Content-Description", StringHelper.urlEncodeUTF8(label)); + exportCourseToZIP(resource, zout); + } catch (Exception e) { + log.error("", e); + } + } + + @Override + public void release() { + // + } + + /** + * Exports an entire course to a zip file. + * + * @param sourceRes + * @param fTargetZIP + * @return true if successfully exported, false otherwise. + */ + private void exportCourseToZIP(OLATResourceable sourceRes, ZipOutputStream outStream) { + PersistingCourseImpl sourceCourse = (PersistingCourseImpl) CourseFactory.loadCourse(sourceRes); + log.info("Start course export: {}", sourceRes); + synchronized (sourceCourse) { //o_clusterNOK - cannot be solved with doInSync since could take too long (leads to error: "Lock wait timeout exceeded") + OLATResource courseResource = sourceCourse.getCourseEnvironment().getCourseGroupManager().getCourseResource(); + exportToFilesystem(courseResource, sourceCourse, outStream); + } + } + + /** + * @see org.olat.course.ICourse#exportToFilesystem(java.io.File) + * <p> + * See OLAT-5368: Course Export can take longer than say 2min. + * <p> + */ + private void exportToFilesystem(OLATResource originalCourseResource, PersistingCourseImpl sourceCourse, ZipOutputStream outStream) { + long s = System.currentTimeMillis(); + + File fExportDir = new File(WebappHelper.getTmpDir(), CodeHelper.getUniqueID()); + try { + fExportDir.mkdirs(); + log.info("exportToFilesystem: exporting course {} to {}", originalCourseResource, fExportDir); + exportToFilesystem(sourceCourse, outStream, fExportDir); + } catch(Exception e) { + log.error("", e); + } finally { + FileUtils.deleteDirsAndFiles(fExportDir, true, true); + } + + log.info("exportToFilesystem: exporting course {} to {} done", originalCourseResource, fExportDir); + log.info("finished export course '{}' in {}s", sourceCourse.getCourseTitle(), Long.toString((System.currentTimeMillis() - s) / 1000l)); + } + + private void exportToFilesystem(PersistingCourseImpl sourceCourse, ZipOutputStream zout, File exportDirectory) { + LocalFolderImpl courseBaseContainer = sourceCourse.getCourseBaseContainer(); + File fCourseBase = courseBaseContainer.getBasefile(); + + ZipUtil.addFileToZip(CourseConfigManager.COURSECONFIG_XML, new File(fCourseBase, CourseConfigManager.COURSECONFIG_XML), zout); + ZipUtil.addFileToZip(PersistingCourseImpl.EDITORTREEMODEL_XML, new File(fCourseBase, PersistingCourseImpl.EDITORTREEMODEL_XML), zout); + ZipUtil.addFileToZip(PersistingCourseImpl.RUNSTRUCTURE_XML, new File(fCourseBase, PersistingCourseImpl.RUNSTRUCTURE_XML), zout); + // export layout and media folder + File layoutDirectory = new File(fCourseBase, "layout"); + if(layoutDirectory.exists()) { + ZipUtil.addPathToZip("layout", layoutDirectory.toPath(), zout); + } + File mediaDirectory = new File(fCourseBase, "media"); + if(mediaDirectory.exists()) { + ZipUtil.addPathToZip("media", mediaDirectory.toPath(), zout); + } + + try { + exportCoursefolder(sourceCourse, zout); + } catch (IOException e) { + log.error("Cannot zip course folder: {}", sourceCourse, e); + } + + //make the folder structure + File fExportedDataDir = new File(exportDirectory, ICourse.EXPORTED_DATA_FOLDERNAME); + fExportedDataDir.mkdirs(); + + exportBusinessGroupData(sourceCourse, fExportedDataDir, zout); + + // export any node data + log.info("exportToFilesystem: exporting course exporting all nodes: {}", sourceCourse); + TreeVisitor tv = new TreeVisitor(node -> exportNode(sourceCourse, fExportedDataDir, node, zout), + sourceCourse.getEditorTreeModel().getRootNode(), true); + tv.visitAll(); + log.info("exportToFilesystem: exporting all course nodes done: {}", sourceCourse); + + // export shared folder + CourseConfig config = sourceCourse.getCourseConfig(); + if (config.hasCustomSharedFolder()) { + exportSharedFolder(config, sourceCourse, fExportedDataDir, zout); + } + // export glossary + if (config.hasGlossary()) { + exportGlossary(config, sourceCourse, fExportedDataDir, zout); + } + + log.info("exportToFilesystem: exporting course configuration and repo data: {}", sourceCourse); + + exportRepositoryEntryMetadata(sourceCourse, zout); + exportReminders(sourceCourse, zout); + + DBFactory.getInstance().commitAndCloseSession(); + } + + private void exportReminders(PersistingCourseImpl sourceCourse, ZipOutputStream zout) { + try { + zout.putNextEntry(new ZipEntry(ZipUtil.concat(ICourse.EXPORTED_DATA_FOLDERNAME, ReminderService.REMINDERS_XML))); + + RepositoryEntry entry = sourceCourse.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + CoreSpringFactory.getImpl(ReminderService.class).exportReminders(entry, zout); + + zout.closeEntry(); + } catch(Exception e) { + log.error("", e); + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportRepositoryEntryMetadata(PersistingCourseImpl sourceCourse, ZipOutputStream zout) { + try { + RepositoryEntry entry = RepositoryManager.getInstance().lookupRepositoryEntry(sourceCourse, true); + RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(entry, null); + importExport.exportDoExportProperties(ICourse.EXPORTED_DATA_FOLDERNAME, zout); + } catch (Exception e) { + log.error("", e); + } finally { + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportGlossary(CourseConfig config, PersistingCourseImpl sourceCourse, File fExportedDataDir, ZipOutputStream zout) { + File glossaryExportDataDir = new File(fExportedDataDir, "glossary"); + try { + glossaryExportDataDir.mkdir(); + + log.info("exportToFilesystem: exporting course glossary: {}", sourceCourse); + final GlossaryManager glossaryManager = CoreSpringFactory.getImpl(GlossaryManager.class); + final CourseConfigManager courseConfigManager = CoreSpringFactory.getImpl(CourseConfigManager.class); + if (!glossaryManager.exportGlossary(config.getGlossarySoftKey(), glossaryExportDataDir)) { + // export failed, delete reference to glossary in the course config + log.info("exportToFilesystem: export of glossary failed."); + config.setGlossarySoftKey(null); + courseConfigManager.saveConfigTo(sourceCourse, config); + } + log.info("exportToFilesystem: exporting course glossary done: {}", sourceCourse); + + ZipUtil.addDirectoryToZip(glossaryExportDataDir.toPath(), ICourse.EXPORTED_DATA_FOLDERNAME, zout); + } catch (Exception e) { + log.error("", e); + } finally { + FileUtils.deleteDirsAndFiles(glossaryExportDataDir, true, true); + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportSharedFolder(CourseConfig config, PersistingCourseImpl sourceCourse, File fExportedDataDir, ZipOutputStream zout) { + File sharedFolderExportDataDir = new File(fExportedDataDir, "sharedfolder"); + try { + sharedFolderExportDataDir.mkdir(); + + log.info("exportToFilesystem: exporting shared folder course: {}", sourceCourse); + if (!SharedFolderManager.getInstance().exportSharedFolder(config.getSharedFolderSoftkey(), sharedFolderExportDataDir)) { + // export failed, delete reference to shared folder in the course config + log.info("exportToFilesystem: export of shared folder failed."); + config.setSharedFolderSoftkey(CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); + CoreSpringFactory.getImpl(CourseConfigManager.class).saveConfigTo(sourceCourse, config); + } + log.info("exportToFilesystem: exporting shared folder course done: {}", sourceCourse); + ZipUtil.addDirectoryToZip(sharedFolderExportDataDir.toPath(), ICourse.EXPORTED_DATA_FOLDERNAME, zout); + } catch (Exception e) { + log.error("", e); + } finally { + FileUtils.deleteDirsAndFiles(sharedFolderExportDataDir, true, true); + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportCoursefolder(PersistingCourseImpl sourceCourse, ZipOutputStream zout) throws IOException { + File courseFolder = sourceCourse.getIsolatedCourseBaseFolder(); + File[] hasChildren = courseFolder.listFiles(SystemFileFilter.DIRECTORY_FILES); + if(hasChildren != null && hasChildren.length > 0) { + zout.putNextEntry(new ZipEntry("oocoursefolder.zip")); + + // export course folder + try(OutputStream shieldedStream = new ShieldOutputStream(zout); + ZipOutputStream exportStream = new ZipOutputStream(shieldedStream)) { + ZipUtil.addPathToZip(courseFolder.toPath(), exportStream); + } catch(Exception e) { + log.error("", e); + } + + zout.closeEntry(); + } + } + + private void exportBusinessGroupData(PersistingCourseImpl sourceCourse, File fExportedDataDir, ZipOutputStream zout) { + File groupExportDataDir = new File(fExportedDataDir, "groups"); + try { + groupExportDataDir.mkdir(); + //export business groups + CourseEnvironmentMapper envMapper = sourceCourse.getCourseEnvironment().getCourseGroupManager().getBusinessGroupEnvironment(); + sourceCourse.getCourseEnvironment().getCourseGroupManager().exportCourseBusinessGroups(groupExportDataDir, envMapper); + ZipUtil.addDirectoryToZip(groupExportDataDir.toPath(), ICourse.EXPORTED_DATA_FOLDERNAME, zout); + } catch (Exception e) { + log.error("", e); + } finally { + FileUtils.deleteDirsAndFiles(groupExportDataDir, true, true); + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportNode(PersistingCourseImpl sourceCourse, File fExportedDataDir, INode node, ZipOutputStream zout) { + CourseEditorTreeNode cNode = (CourseEditorTreeNode) node; + CourseNode courseNode = cNode.getCourseNode(); + if(courseNode instanceof ScormCourseNode + || courseNode instanceof CPCourseNode) { + exportCPCourseNode(courseNode, zout); + } else if(courseNode instanceof VideoCourseNode) { + exportVideoCourseNode((VideoCourseNode)courseNode, zout); + } else if(courseNode instanceof BCCourseNode) { + exportBCCourseNode(sourceCourse, (BCCourseNode)courseNode, zout); + } else { + exportCourseNode(sourceCourse, fExportedDataDir, courseNode, zout); + } + } + + private void exportCourseNode(PersistingCourseImpl sourceCourse, File fExportedDataDir, CourseNode courseNode, ZipOutputStream zout) { + File nodeExportDataDir = new File(fExportedDataDir, "node_" + courseNode.getIdent()); + try { + nodeExportDataDir.mkdir(); + + courseNode.exportNode(nodeExportDataDir, sourceCourse); + ZipUtil.addDirectoryToZip(nodeExportDataDir.toPath(), ICourse.EXPORTED_DATA_FOLDERNAME, zout); + } catch (Exception e) { + log.error("", e); + } finally { + FileUtils.deleteDirsAndFiles(nodeExportDataDir, true, true); + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportBCCourseNode(PersistingCourseImpl sourceCourse, BCCourseNode courseNode, ZipOutputStream zout) { + try(ShieldOutputStream fOut = new ShieldOutputStream(zout)) { + VFSContainer nodeContainer = VFSManager.olatRootContainer(BCCourseNode.getFoldernodePathRelToFolderBase(sourceCourse.getCourseEnvironment(), courseNode), null); + + String nodeDirectory = ZipUtil.concat(ICourse.EXPORTED_DATA_FOLDERNAME, courseNode.getIdent()); + zout.putNextEntry(new ZipEntry(ZipUtil.concat(nodeDirectory, "oonode.zip"))); + + ZipUtil.zip(nodeContainer, fOut); + + zout.closeEntry(); + } catch (IOException e) { + log.error("", e); + } finally { + DBFactory.getInstance().commitAndCloseSession(); + } + } + + /** + * Export video course node + * @param courseNode + * @param zout + */ + private void exportVideoCourseNode(VideoCourseNode courseNode, ZipOutputStream zout) { + try { + RepositoryEntry videoEntry = VideoEditController.getVideoReference(courseNode.getModuleConfiguration(), false); + exportCourseNodeWithRepositoryEntry(videoEntry, courseNode, zout); + } catch(Exception e) { + log.error("", e); + } finally { + DBFactory.getInstance().commitAndCloseSession(); + } + } + + /** + * Export course node with IMS CP or SCORM packages + * @param courseNode + * @param zout + */ + private void exportCPCourseNode(CourseNode courseNode, ZipOutputStream zout) { + try { + RepositoryEntry scormEntry = CPEditController.getCPReference(courseNode.getModuleConfiguration(), false); + exportCourseNodeWithRepositoryEntry(scormEntry, courseNode, zout); + } catch(Exception e) { + log.error("", e); + } finally { + DBFactory.getInstance().commitAndCloseSession(); + } + } + + private void exportCourseNodeWithRepositoryEntry(RepositoryEntry entry, CourseNode courseNode, ZipOutputStream zout) { + if(entry != null) { + RepositoryEntryImportExport reie = new RepositoryEntryImportExport(entry, null); + reie.exportDoExport(ZipUtil.concat(ICourse.EXPORTED_DATA_FOLDERNAME, courseNode.getIdent()), zout); + } + } +} diff --git a/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java b/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java index bf41516382d..2ee6bf84331 100644 --- a/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java +++ b/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java @@ -263,7 +263,7 @@ public interface CourseGroupManager { * * @param fExportDirectory */ - public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper env, boolean runtimeDatas); + public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper env); public CourseEnvironmentMapper getBusinessGroupEnvironment(); diff --git a/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java b/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java index 61853b79d99..d0754bb7b27 100644 --- a/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java +++ b/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java @@ -367,7 +367,7 @@ public class PersistingCourseGroupManager implements CourseGroupManager { } @Override - public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper courseEnv, boolean runtimeDatas) { + public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper courseEnv) { File fExportFile = new File(fExportDirectory, LEARNINGGROUPEXPORT_XML); List<BGArea> areas = getAllAreas(); List<BusinessGroup> groups = getAllBusinessGroups(); @@ -375,7 +375,7 @@ public class PersistingCourseGroupManager implements CourseGroupManager { BusinessGroupEnvironment bgEnv = new BusinessGroupEnvironment(); bgEnv.getGroups().addAll(courseEnv.getGroups()); bgEnv.getAreas().addAll(courseEnv.getAreas()); - businessGroupService.exportGroups(groups, areas, fExportFile, runtimeDatas); + businessGroupService.exportGroups(groups, areas, fExportFile, false); } /** diff --git a/src/main/java/org/olat/course/run/preview/PreviewCourseGroupManager.java b/src/main/java/org/olat/course/run/preview/PreviewCourseGroupManager.java index 8ed18c299bb..9d87eb121e2 100644 --- a/src/main/java/org/olat/course/run/preview/PreviewCourseGroupManager.java +++ b/src/main/java/org/olat/course/run/preview/PreviewCourseGroupManager.java @@ -373,7 +373,7 @@ final class PreviewCourseGroupManager implements CourseGroupManager { } @Override - public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper env, boolean runtimeDatas) { + public void exportCourseBusinessGroups(File fExportDirectory, CourseEnvironmentMapper env) { throw new AssertException("unsupported"); } diff --git a/src/main/java/org/olat/modules/portfolio/handler/BinderTemplateMediaResource.java b/src/main/java/org/olat/modules/portfolio/handler/BinderTemplateMediaResource.java index 6b151b81105..49d4f3b2797 100644 --- a/src/main/java/org/olat/modules/portfolio/handler/BinderTemplateMediaResource.java +++ b/src/main/java/org/olat/modules/portfolio/handler/BinderTemplateMediaResource.java @@ -131,7 +131,7 @@ public class BinderTemplateMediaResource implements MediaResource { OLATResource resource = templateEntry.getOlatResource(); File baseContainer= FileResourceManager.getInstance().getFileResource(resource); RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(templateEntry, baseContainer); - importExport.exportDoExportProperties(zout); + importExport.exportDoExportProperties("", zout); } catch (Exception e) { log.error("", e); } diff --git a/src/main/java/org/olat/modules/reminder/ReminderService.java b/src/main/java/org/olat/modules/reminder/ReminderService.java index 67ef306189a..7e46745a956 100644 --- a/src/main/java/org/olat/modules/reminder/ReminderService.java +++ b/src/main/java/org/olat/modules/reminder/ReminderService.java @@ -20,6 +20,7 @@ package org.olat.modules.reminder; import java.io.File; +import java.io.OutputStream; import java.util.List; import org.olat.core.id.Identity; @@ -85,7 +86,7 @@ public interface ReminderService { public ReminderRules toRules(String rulesXml); - public void exportReminders(RepositoryEntry entry, File fExportedDataDir); + public void exportReminders(RepositoryEntryRef entry, OutputStream fExportedDataDir); /** * The reminders are not persisted and not converted to any new course, group... diff --git a/src/main/java/org/olat/modules/reminder/manager/ReminderServiceImpl.java b/src/main/java/org/olat/modules/reminder/manager/ReminderServiceImpl.java index e406a07fd0a..fd97b219c23 100644 --- a/src/main/java/org/olat/modules/reminder/manager/ReminderServiceImpl.java +++ b/src/main/java/org/olat/modules/reminder/manager/ReminderServiceImpl.java @@ -21,7 +21,6 @@ package org.olat.modules.reminder.manager; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.text.ParseException; @@ -31,13 +30,13 @@ import java.util.List; import java.util.Locale; import java.util.UUID; +import org.apache.logging.log4j.Logger; import org.apache.velocity.VelocityContext; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.User; import org.olat.core.id.UserConstants; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; @@ -173,19 +172,17 @@ public class ReminderServiceImpl implements ReminderService { } @Override - public void exportReminders(RepositoryEntry entry, File fExportedDataDir) { + public void exportReminders(RepositoryEntryRef entry, OutputStream fOut) { List<Reminder> reminders = reminderDao.getReminders(entry); - if(reminders.size() > 0) { - try (OutputStream fOut = new FileOutputStream(new File(fExportedDataDir, REMINDERS_XML))) { - ImportExportReminders exportReminders = new ImportExportReminders(); - for(Reminder reminder:reminders) { - ImportExportReminder exportReminder = new ImportExportReminder(reminder); - exportReminders.getReminders().add(exportReminder); - } - ReminderRulesXStream.toXML(exportReminders, fOut); - } catch(Exception e) { - log.error("", e); + try { + ImportExportReminders exportReminders = new ImportExportReminders(); + for(Reminder reminder:reminders) { + ImportExportReminder exportReminder = new ImportExportReminder(reminder); + exportReminders.getReminders().add(exportReminder); } + ReminderRulesXStream.toXML(exportReminders, fOut); + } catch(Exception e) { + log.error("", e); } } diff --git a/src/main/java/org/olat/repository/RepositoryEntryImportExport.java b/src/main/java/org/olat/repository/RepositoryEntryImportExport.java index eced9bc3c81..dacf8b033a8 100644 --- a/src/main/java/org/olat/repository/RepositoryEntryImportExport.java +++ b/src/main/java/org/olat/repository/RepositoryEntryImportExport.java @@ -41,6 +41,7 @@ import java.util.zip.ZipOutputStream; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.Logger; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.license.LicenseService; import org.olat.core.commons.services.license.LicenseType; @@ -48,10 +49,10 @@ import org.olat.core.commons.services.license.ResourceLicense; import org.olat.core.commons.services.license.ui.LicenseUIFactory; import org.olat.core.gui.media.MediaResource; import org.olat.core.logging.OLATRuntimeException; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; +import org.olat.core.util.ZipUtil; import org.olat.core.util.io.HttpServletResponseOutputStream; import org.olat.core.util.io.ShieldOutputStream; import org.olat.core.util.vfs.LocalFileImpl; @@ -141,6 +142,39 @@ public class RepositoryEntryImportExport { exportDoExportProperties(); return exportDoExportContent(); } + + public boolean exportDoExport(String zipPath, ZipOutputStream zout) { + exportDoExportProperties(zipPath, zout); + return exportDoExportContent(zipPath, zout); + } + + public void exportDoExportProperties(String zipPath, ZipOutputStream zout) { + // save repository entry properties + RepositoryEntryImport imp = new RepositoryEntryImport(re); + RepositoryManager rm = RepositoryManager.getInstance(); + VFSLeaf image = rm.getImage(re); + if(image instanceof LocalFileImpl) { + imp.setImageName(image.getName()); + ZipUtil.addFileToZip(ZipUtil.concat(zipPath, image.getName()) , ((LocalFileImpl)image).getBasefile(), zout); + } + + RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); + VFSLeaf movie = repositoryService.getIntroductionMovie(re); + if(movie instanceof LocalFileImpl) { + imp.setMovieName(movie.getName()); + ZipUtil.addFileToZip(ZipUtil.concat(zipPath, movie.getName()), ((LocalFileImpl)movie).getBasefile(), zout); + } + + try(ShieldOutputStream fOut = new ShieldOutputStream(zout)) { + addLicenseInformations(imp, re); + zout.putNextEntry(new ZipEntry(ZipUtil.concat(zipPath, PROPERTIES_FILE))); + xstream.toXML(imp, fOut); + zout.closeEntry(); + } catch (IOException ioe) { + throw new OLATRuntimeException("Error writing repo properties.", ioe); + } + } + /** * Export metadata of a repository entry to a file. * Only one repository entry's metadata may be exported into a directory. The @@ -184,45 +218,6 @@ public class RepositoryEntryImportExport { } } - public void exportDoExportProperties(ZipOutputStream zout) throws IOException { - RepositoryEntryImport imp = new RepositoryEntryImport(re); - RepositoryManager rm = RepositoryManager.getInstance(); - VFSLeaf image = rm.getImage(re); - if(image != null) { - imp.setImageName(image.getName()); - zout.putNextEntry(new ZipEntry(image.getName())); - try(InputStream inImage=image.getInputStream(); - OutputStream out = new ShieldOutputStream(zout)) { - FileUtils.copy(inImage, out); - } catch(Exception e) { - log.error("", e); - } - zout.closeEntry(); - } - - RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); - VFSLeaf movie = repositoryService.getIntroductionMovie(re); - if(movie != null) { - imp.setMovieName(movie.getName()); - zout.putNextEntry(new ZipEntry(movie.getName())); - try(InputStream inMovie=movie.getInputStream(); - OutputStream out=new ShieldOutputStream(zout)) { - FileUtils.copy(inMovie, out); - } catch(Exception e) { - log.error("", e); - } - zout.closeEntry(); - } - - zout.putNextEntry(new ZipEntry(PROPERTIES_FILE)); - try(OutputStream out=new ShieldOutputStream(zout)) { - xstream.toXML(imp, out); - } catch(IOException e) { - log.error("", e); - } - zout.closeEntry(); - } - /** * Export a repository entry referenced by a course node to the given export directory. * User importReferencedRepositoryEntry to import again. @@ -250,6 +245,29 @@ public class RepositoryEntryImportExport { return true; } + public boolean exportDoExportContent(String zipPath, ZipOutputStream zout) { + // export resource + RepositoryHandler rh = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); + MediaResource mr = rh.getAsMediaResource(re.getOlatResource()); + try(OutputStream fOut = new ShieldOutputStream(zout); + InputStream in = mr.getInputStream()) { + zout.putNextEntry(new ZipEntry(ZipUtil.concat(zipPath, CONTENT_FILE))); + if(in == null) { + HttpServletResponse hres = new HttpServletResponseOutputStream(fOut); + mr.prepare(hres); + } else { + IOUtils.copy(in, fOut); + } + fOut.flush(); + zout.closeEntry(); + } catch (IOException fnfe) { + return false; + } finally { + mr.release(); + } + return true; + } + public RepositoryEntry importContent(RepositoryEntry newEntry, VFSContainer mediaContainer) { if(!anyExportedPropertiesAvailable()) return newEntry; diff --git a/src/main/java/org/olat/repository/handlers/CourseHandler.java b/src/main/java/org/olat/repository/handlers/CourseHandler.java index 4deba24665c..91c3fc64321 100644 --- a/src/main/java/org/olat/repository/handlers/CourseHandler.java +++ b/src/main/java/org/olat/repository/handlers/CourseHandler.java @@ -50,7 +50,6 @@ import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; -import org.olat.core.gui.media.CleanupAfterDeliveryFileMediaResource; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; @@ -62,7 +61,6 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; import org.olat.core.util.PathUtils; -import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.core.util.ZipUtil; @@ -85,6 +83,7 @@ import org.olat.course.Structure; import org.olat.course.config.CourseConfig; import org.olat.course.editor.CourseAccessAndProperties; import org.olat.course.export.CourseEnvironmentMapper; +import org.olat.course.export.CourseExportMediaResource; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.groupsandrights.PersistingCourseGroupManager; import org.olat.course.run.CourseRuntimeController; @@ -461,7 +460,7 @@ public class CourseHandler implements RepositoryHandler { File fExportDir = new File(WebappHelper.getTmpDir(), UUID.randomUUID().toString()); fExportDir.mkdirs(); - sourceCgm.exportCourseBusinessGroups(fExportDir, env, false); + sourceCgm.exportCourseBusinessGroups(fExportDir, env); ICourse course = CourseFactory.loadCourse(target); CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager(); @@ -546,11 +545,7 @@ public class CourseHandler implements RepositoryHandler { @Override public MediaResource getAsMediaResource(OLATResourceable res) { - RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(res, true); - String exportFileName = StringHelper.transformDisplayNameToFileSystemName(re.getDisplayname()) + ".zip"; - File fExportZIP = new File(WebappHelper.getTmpDir(), exportFileName); - CourseFactory.exportCourseToZIP(res, fExportZIP, false); - return new CleanupAfterDeliveryFileMediaResource(fExportZIP); + return new CourseExportMediaResource(res); } @Override -- GitLab