From d61e33e6e112853da69f46e44ae39c25fd2ff629 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 31 Oct 2014 14:15:46 +0100
Subject: [PATCH] OO-1283: download as zip in folders

---
 .../bc/_i18n/LocalStrings_de.properties       |   1 +
 .../bc/_i18n/LocalStrings_en.properties       |   1 +
 .../bc/_i18n/LocalStrings_fr.properties       |   1 +
 .../modules/bc/commands/CmdDownloadZip.java   | 179 ++++++++++++++++++
 .../bc/commands/FolderCommandFactory.java     |   2 +
 .../components/FolderComponentRenderer.java   |   6 +
 .../modules/bc/components/ListRenderer.java   |  24 +--
 7 files changed, 200 insertions(+), 14 deletions(-)
 create mode 100644 src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java

diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
index f95bb83ae0d..9148c07a8bf 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
@@ -95,6 +95,7 @@ del.header=L\u00F6schen best\u00E4tigen
 del.partial=Einige der Dateien konnten nicht gel\u00F6scht werden.
 dfiles=Gel\u00F6schte Dateien
 displayname.user=Pers\u00F6nlicher Ordner von {0}
+download=Hochladen
 edit=Bearbeiten
 editQuota=Quota anpassen
 editQuota.nop=F\u00FCr dieses Objekt kann keine Quota gesetzt werden.
diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
index 9b2057d83b8..94299cf0aa0 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
@@ -94,6 +94,7 @@ del=Delete
 del.confirm=Do you really want to delete the following files?
 del.header=Confirm deletion
 del.partial=Some files could not be deleted.
+download=Download
 dfiles=Deleted files
 displayname.user=Personal folder of {0}
 edit=Edit
diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_fr.properties
index 15de12ef408..11ac34d2aec 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_fr.properties
@@ -95,6 +95,7 @@ del.header=Confirmation de suppression
 del.partial=Quelques uns des fichiers n'ont pas pu \u00EAtre supprim\u00E9s.
 dfiles=Fichiers effac\u00E9s
 displayname.user=Dossier personnel de {0}
+download=T\u00E9l\u00E9charger
 edit=Editer
 editQuota=Adapter les quotas
 editQuota.nop=Il est impossible de poser un quota pour cet objet.
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
new file mode 100644
index 00000000000..8508c04f04a
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
@@ -0,0 +1,179 @@
+/**
+ * <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.core.commons.modules.bc.commands;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.olat.core.commons.modules.bc.FileSelection;
+import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.logging.AssertException;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.ZipUtil;
+import org.olat.core.util.vfs.VFSConstants;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSItem;
+
+/**
+ * 
+ * Initial date: 31.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CmdDownloadZip implements FolderCommand {
+	
+	private static final OLog log = Tracing.createLoggerFor(CmdDownloadZip.class);
+
+	private FileSelection selection;
+	private VFSContainer currentContainer;
+	private int status = FolderCommandStatus.STATUS_SUCCESS;
+
+	
+	@Override
+	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator trans) {
+		currentContainer = folderComponent.getCurrentContainer();
+		if (currentContainer.canWrite() != VFSConstants.YES) {
+			throw new AssertException("Cannot write to current folder.");
+		}
+		
+		status = FolderCommandHelper.sanityCheck(wControl, folderComponent);
+		if(status == FolderCommandStatus.STATUS_FAILED) {
+			return null;
+		}
+	
+		selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath());
+		status = FolderCommandHelper.sanityCheck3(wControl, folderComponent, selection);
+		if(status == FolderCommandStatus.STATUS_FAILED) {
+			return null;
+		}
+		
+		MediaResource mr = new ZipMediaResource(currentContainer, selection);
+		ureq.getDispatchResult().setResultingMediaResource(mr);
+		return null;
+	}
+
+
+	public int getStatus() {
+		return status;
+	}
+
+	@Override
+	public boolean runsModal() {
+		return false;
+	}
+
+	@Override
+	public String getModalTitle() {
+		return null;
+	}
+
+	private static class ZipMediaResource implements MediaResource {
+		
+		private final FileSelection selection;
+		private final VFSContainer currentContainer;
+		
+		public ZipMediaResource(VFSContainer currentContainer, FileSelection selection) {
+			this.selection = selection;
+			this.currentContainer = currentContainer;
+		}
+
+		@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) {
+			List<String> selectedFiles = selection.getFiles();
+			
+			String urlEncodedLabel;
+			if(selectedFiles.size() == 1) {
+				String filename = selectedFiles.get(0);
+				int lastIndexOf = filename.lastIndexOf('.');
+				if(lastIndexOf > 0) {
+					filename = filename.substring(0, lastIndexOf);
+				}
+				urlEncodedLabel = StringHelper.urlEncodeUTF8(filename + ".zip");
+			} else  {
+				urlEncodedLabel = "Archive.zip";
+			}
+			
+			hres.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + urlEncodedLabel);			
+			hres.setHeader("Content-Description", urlEncodedLabel);
+			
+			ZipOutputStream zout = null;
+			try {
+				zout = new ZipOutputStream(hres.getOutputStream());
+				zout.setLevel(9);
+				
+				List<VFSItem> vfsFiles = new ArrayList<VFSItem>();
+				for (String fileName : selectedFiles) {
+					VFSItem item = currentContainer.resolve(fileName);
+					if (item != null) {
+						vfsFiles.add(item);
+					}
+				}
+				
+				boolean success = true;
+				for (Iterator<VFSItem> iter = vfsFiles.iterator(); success && iter.hasNext();) {
+					success = ZipUtil.addToZip(iter.next(), "", zout);
+				}
+				zout.flush();
+			} catch (Exception e) {
+				log.error("", e);
+			} finally {
+				IOUtils.closeQuietly(zout);
+			}
+		}
+
+		@Override
+		public void release() {
+			//
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java b/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java
index 6b4b302247f..a4e015cf985 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java
@@ -54,6 +54,7 @@ public class FolderCommandFactory {
 	public static final String COMMAND_COPY = "copy";
 	public static final String COMMAND_DEL = "del";
 	public static final String COMMAND_MAIL = "mail";
+	public static final String COMMAND_DOWNLOAD_ZIP = "dzip";
 	public static final String COMMAND_ZIP = "zip";
 	public static final String COMMAND_UNZIP = "unzip";
 
@@ -87,6 +88,7 @@ public class FolderCommandFactory {
 		else if (command.equals(COMMAND_MOVE)) cmd = new CmdMoveCopy(wControl, true);
 		else if (command.equals(COMMAND_COPY)) cmd = new CmdMoveCopy(wControl, false);
 		else if (command.equals(COMMAND_ZIP)) cmd = new CmdZip(ureq,wControl);
+		else if (command.equals(COMMAND_DOWNLOAD_ZIP)) cmd = new CmdDownloadZip();
 		else if (command.equals(COMMAND_UNZIP)) cmd = new CmdUnzip(ureq,wControl);
 		else if (command.equals(COMMAND_VIEW_VERSION)) cmd = new CmdViewRevisions(ureq,wControl);
 		else if (command.equals(COMMAND_ADD_EPORTFOLIO)) {
diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java
index d87a577451c..b647fb16599 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java
@@ -232,6 +232,12 @@ public class FolderComponentRenderer extends DefaultComponentRenderer {
 					target.append("\" />");
 				}
 				
+				target.append("<input type=\"submit\" class='btn btn-default' name=\"");
+				target.append(FolderRunController.ACTION_PRE).append(FolderCommandFactory.COMMAND_DOWNLOAD_ZIP);
+				target.append("\" value=\"");
+				target.append(StringHelper.escapeHtml(translator.translate("download")));
+				target.append("\" />");
+				
 				if (canDelete) {
 					// delete
 					target.append("<input type=\"submit\" class='btn btn-default' name=\"");
diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
index e84b323d3f2..38f629375ec 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
@@ -204,8 +204,6 @@ public class ListRenderer {
 	
 		// assume full access unless security callback tells us something different.
 		boolean canWrite = child.getParentContainer().canWrite() == VFSConstants.YES;
-		boolean canDelete = child.getParentContainer().canDelete() == VFSConstants.YES;
-		boolean canMail = fc.isCanMail();
 		boolean isAbstract = (child instanceof AbstractVirtualContainer);
 
 		Versions versions = null;
@@ -234,24 +232,22 @@ public class ListRenderer {
 		
 		String name = child.getName();
 		String pathAndName = currentContainerPath;
-		if (pathAndName.length() > 0 && !pathAndName.endsWith("/"))
+		if (pathAndName.length() > 0 && !pathAndName.endsWith("/")) {
 			pathAndName = pathAndName + "/";
+		}
 		pathAndName = pathAndName + name;
 				
 		// tr begin
-		sb.append("<tr><td>");
-
+		sb.append("<tr><td>")
 		// add checkbox for actions if user can write, delete or email this directory
-		if (canWrite || canDelete || canMail) {
-			sb.append("<input type=\"checkbox\" name=\"");
-			sb.append(FileSelection.FORM_ID);
-			sb.append("\" value=\"");
-			sb.append(StringHelper.escapeHtml(name));
-			sb.append("\" /> ");
-		}		
-		
+		  .append("<input type=\"checkbox\" name=\"")
+		  .append(FileSelection.FORM_ID)
+		  .append("\" value=\"")
+		  .append(StringHelper.escapeHtml(name))
+		  .append("\" /> ")
 		// browse link pre
-		sb.append("<a id='o_sel_doc_").append(pos).append("' href=\"");
+		  .append("<a id='o_sel_doc_").append(pos).append("' href=\"");
+		
 		if (isContainer) { // for directories... normal module URIs
 			ubu.buildURI(sb, null, null, pathAndName, iframePostEnabled ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL);
 			sb.append("\"");
-- 
GitLab