/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <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>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
* <p>
*/
package org.olat.core.commons.modules.bc;
import static java.util.Arrays.asList;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.olat.core.commons.modules.bc.commands.FolderCommandStatus;
import org.olat.core.commons.modules.bc.meta.MetaInfo;
import org.olat.core.commons.modules.bc.meta.MetaInfoFactory;
import org.olat.core.commons.modules.bc.meta.MetaInfoFormController;
import org.olat.core.commons.modules.bc.version.RevisionListController;
import org.olat.core.commons.modules.bc.version.VersionCommentController;
import org.olat.core.commons.modules.bc.vfs.OlatRootFileImpl;
import org.olat.core.commons.services.image.ImageService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FileElement;
import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.Form;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.form.flexible.impl.elements.FileElementEvent;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
import org.olat.core.gui.control.generic.modal.ButtonClickedEvent;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.id.Roles;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.activity.CoreLoggingResourceable;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.FileUtils;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.ValidationStatus;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.vfs.LocalImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSLockManager;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.version.Versionable;
import org.olat.core.util.vfs.version.Versions;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <h3>Description</h3>
* <p>
* This controller offers a file upload in a dedicated form. It can be
* configured with an upload limit, a limitation to mime types as allowed upload
* types and if the path to the target directory should be displayed in the
* form.
*
* <h3>Events fired by this controller</h3>
* <ul>
* <li>FolderEvent (whenever something like upload occures)</li>
* <li>Event.CANCELLED_EVENT</li>
* <li>Event.FAILED_EVENT</li>
* <li>Event.DONE_EVENT (fired after the folder upload event)</li>
* </ul>
* <p>
*
* Initial Date: August 15, 2005
*
* @author Alexander Schneider
* @author Florian Gnägi
*/
public class FileUploadController extends FormBasicController {
private int status = FolderCommandStatus.STATUS_SUCCESS;
private VFSContainer currentContainer;
private VFSContainer uploadVFSContainer;
private String uploadRelPath = null;
private RevisionListController revisionListCtr;
private CloseableModalController revisionListDialogBox, commentVersionDialogBox, unlockDialogBox;
private VersionCommentController commentVersionCtr;
private VersionCommentController unlockCtr;
private DialogBoxController overwriteDialog;
private DialogBoxController lockedFileDialog;
private VFSLeaf newFile;
private VFSItem existingVFSItem;
private long uploadLimitKB;
private long remainingQuotKB;
private Set<String> mimeTypes;
private boolean uriValidation;
//
// Form elements
private FileElement fileEl;
private MultipleSelectionElement resizeEl;
private StaticTextElement pathEl;
private boolean showTargetPath = false;
private boolean showTitle = true;
private boolean fileOverwritten = false;
private boolean resizeImg;
// Metadata subform
private MetaInfoFormController metaDataCtr;
private boolean showMetadata = false;
//
// Cancel button
private boolean showCancel = true; // default is to show cancel button
private static Pattern imageExtPattern = Pattern.compile("\\b.(jpg|jpeg|png)\\b");
private static final Pattern validSubPathPattern = Pattern.compile("[\\p{Alnum}-_\\./]*");
@Autowired
private ImageService imageHelper;
@Autowired
private FilesInfoMBean fileInfoMBean;
@Autowired
private VFSLockManager vfsLockManager;
@Autowired
private MetaInfoFactory metaInfoFactory;
private String subfolderPath;
private TextElement targetSubPath ;
/**
* @param wControl
* @param curContainer Path to the upload directory. Used to check for
* existing files with same name and for displaying the optional
* targetPath
* @param ureq
* @param upLimitKB the max upload file size in kBytes (e.g. 10*1024*1024 for
* 10MB)
* @param remainingQuotKB the available space left for file upload kBytes
* (e.g. 10*1024*1024 for 10MB). Quota.UNLIMITED for no limitation, 0
* for no more space left
* @param mimeTypes Set of supported mime types (image/*, image/jpg) or NULL
* if no restriction should be applied.
* @param showTargetPath true: show the relative path where the file will be
* uploaded to; false: show no path
*/
public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
Set<String> mimeTypesRestriction, boolean showTargetPath) {
this(wControl, curContainer, ureq, upLimitKB, remainingQuotKB, mimeTypesRestriction, false, showTargetPath, false, true, true, true);
}
public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle) {
this(wControl,curContainer, ureq, upLimitKB, remainingQuotKB,
mimeTypesRestriction, uriValidation, showTargetPath, showMetadata, resizeImg, showCancel, showTitle,null);
}
public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle, String subfolderPath) {
super(ureq, wControl, "file_upload");
setVariables(curContainer, upLimitKB, remainingQuotKB, mimeTypesRestriction, uriValidation, showTargetPath, showMetadata, resizeImg, showCancel, showTitle, subfolderPath);
initForm(ureq);
}
private void setVariables(VFSContainer curContainer, long upLimitKB, long remainingQuotKB, Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath,
boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle, String subfolderPath) {
this.currentContainer = curContainer;
this.mimeTypes = mimeTypesRestriction;
this.showTitle = showTitle;
this.showTargetPath = showTargetPath;
// set remaining quota and max upload size
this.uploadLimitKB = upLimitKB;
this.uriValidation = uriValidation;
this.remainingQuotKB = remainingQuotKB;
// use base container as upload dir
this.uploadRelPath = null;
this.uploadVFSContainer = this.currentContainer;
this.resizeImg = resizeImg;
this.showMetadata = showMetadata;
this.showCancel = showCancel;
this.subfolderPath = subfolderPath;
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
// Trigger fieldset and title
if(showTitle) {
setFormTitle("ul.header");
}
flc.contextPut("showMetadata", showMetadata);
// Add file element
FormItemContainer fileUpload;
// the layout of the file upload depends on the metadata. if they're
// shown, align the file upload element
if (showMetadata) {
fileUpload = FormLayoutContainer.createDefaultFormLayout("file_upload", getTranslator());
} else {
fileUpload = FormLayoutContainer.createVerticalFormLayout("file_upload", getTranslator());
}
formLayout.add(fileUpload);
flc.contextPut("resizeImg", resizeImg);
// Add path element
if (showTargetPath) {
String path = "/ " + StringHelper.escapeHtml(uploadVFSContainer.getName());
VFSContainer container = uploadVFSContainer.getParentContainer();
while (container != null) {
path = "/ " + StringHelper.escapeHtml(container.getName()) + " " + path;
container = container.getParentContainer();
}
pathEl = uifactory.addStaticTextElement("ul.target", path,fileUpload);
if (subfolderPath != null) {
targetSubPath = uifactory.addInlineTextElement("ul.target.child", subfolderPath, fileUpload, this);
targetSubPath.setLabel("ul.target.child", null);
}
}
fileEl = uifactory.addFileElement(getWindowControl(), "fileEl", "ul.file", fileUpload);
fileEl.addActionListener(FormEvent.ONCHANGE);
setMaxUploadSizeKB((uploadLimitKB < remainingQuotKB ? uploadLimitKB : remainingQuotKB));
fileEl.setMandatory(true, "NoFileChoosen");
if (mimeTypes != null && mimeTypes.size() > 0) {
fileEl.limitToMimeType(mimeTypes, "WrongMimeType", new String[]{mimeTypes.toString()});
}
if(resizeImg) {
FormLayoutContainer resizeCont;
if (showMetadata) {
resizeCont = FormLayoutContainer.createDefaultFormLayout("resize_image_wrapper", getTranslator());
} else {
resizeCont = FormLayoutContainer.createVerticalFormLayout("resize_image_wrapper", getTranslator());
}
formLayout.add(resizeCont);
String[] keys = new String[]{"resize"};
String[] values = new String[]{translate("resize_image")};
resizeEl = uifactory.addCheckboxesHorizontal("resize_image", resizeCont, keys, values);
resizeEl.setLabel(null, null);
resizeEl.select("resize", true);
}
// Check remaining quota
if (remainingQuotKB == 0) {
fileEl.setEnabled(false);
getWindowControl().setError(translate("QuotaExceeded"));
}
if (showMetadata) {
metaDataCtr = new MetaInfoFormController(ureq, getWindowControl(),
mainForm);
formLayout.add("metadata", metaDataCtr.getFormItem());
listenTo(metaDataCtr);
}
// Add cancel and submit in button group layout
FormItemContainer buttons;
if (showMetadata) {
buttons = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator());
} else {
buttons = FormLayoutContainer.createVerticalFormLayout("buttons", getTranslator());
}
formLayout.add(buttons);
FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
buttons.add(buttonGroupLayout);
buttonGroupLayout.setElementCssClass("o_sel_upload_buttons");
uifactory.addFormSubmitButton("ul.upload", buttonGroupLayout);
if (showCancel) {
uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
}
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(fileEl == source) {
if(FileElementEvent.DELETE.equals(event.getCommand())) {
fileEl.reset();
fileEl.setDeleteEnabled(false);
fileEl.clearError();
} else if(metaDataCtr != null) {
String filename = fileEl.getUploadFileName();
if(filename == null) {
metaDataCtr.getFilenameEl().setExampleKey("mf.filename.warning", null);
} else if(!FileUtils.validateFilename(filename)) {
String suffix = FileUtils.getFileSuffix(filename);
if(suffix != null && suffix.length() > 0) {
filename = filename.substring(0, filename.length() - suffix.length() - 1);
}
filename = FileUtils.normalizeFilename(filename) + "." + suffix;
metaDataCtr.getFilenameEl().setExampleKey("mf.filename.warning", null);
}
metaDataCtr.setFilename(filename);
}
}
super.formInnerEvent(ureq, source, event);
}
@Override
protected void formOK(UserRequest ureq) {
if(targetSubPath != null) setUploadRelPath(targetSubPath.getValue());
if ( fileEl.isUploadSuccess()) {
doUpload(ureq);
} else {
if (mainForm.getLastRequestError() == Form.REQUEST_ERROR_GENERAL ) {
showError("failed");
} else if (mainForm.getLastRequestError() == Form.REQUEST_ERROR_FILE_EMPTY ) {
showError("failed");
} else if (mainForm.getLastRequestError() == Form.REQUEST_ERROR_UPLOAD_LIMIT_EXCEEDED) {
showError("QuotaExceeded");
}
status = FolderCommandStatus.STATUS_FAILED;
fireEvent(ureq, Event.FAILED_EVENT);
}
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formCancelled(org.olat.core.gui.UserRequest)
*/
@Override
protected void formCancelled(UserRequest ureq) {
status = FolderCommandStatus.STATUS_CANCELED;
fireEvent(ureq, Event.CANCELLED_EVENT);
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
*/
@Override
public void event(UserRequest ureq, Controller source, Event event) {
if (source == overwriteDialog) {
if (event instanceof ButtonClickedEvent) {
ButtonClickedEvent buttonClickedEvent = (ButtonClickedEvent) event;
if (buttonClickedEvent.getPosition() == 0) { //ok
doFinishOverwrite(ureq);
} else if (buttonClickedEvent.getPosition() == 1) { //not ok
// Upload renamed. Since we've already uploaded the file with a changed name, don't do anything much here...
fileOverwritten = true;
// ... and notify listeners.
finishUpload(ureq);
} else if (buttonClickedEvent.getPosition() == 2) { // cancel
// Cancel. Remove the new file since it has already been uploaded. Note that we don't have to explicitly close the
// dialog box since it closes itself whenever something gets clicked.
doCancel(ureq);
}
}
} else if (source == lockedFileDialog) {
if (event instanceof ButtonClickedEvent) {
ButtonClickedEvent buttonClickedEvent = (ButtonClickedEvent) event;
if(buttonClickedEvent.getPosition() == 0) {
//upload the file with a new name
fileOverwritten = true;
// ... and notify listeners.
finishUpload(ureq);
} else if(buttonClickedEvent.getPosition() == 1) {
doCancel(ureq);
}
}
} else if (source == commentVersionCtr) {
doFinishComment(ureq);
} else if (source == unlockCtr) {
// Overwrite...
String fileName = existingVFSItem.getName();
if(!unlockCtr.keepLocked()) {
vfsLockManager.unlock(existingVFSItem, getIdentity(), ureq.getUserSession().getRoles());
}
unlockDialogBox.deactivate();
existingVFSItem.delete();
newFile.rename(fileName);
// ... and notify listeners.
finishUpload(ureq);
} else if (source == revisionListDialogBox) {
removeAsListenerAndDispose(revisionListCtr);
revisionListCtr = null;
removeAsListenerAndDispose(revisionListDialogBox);
revisionListDialogBox = null;
doCancel(ureq);
} else if (source == revisionListCtr) {
revisionListDialogBox.deactivate();
removeAsListenerAndDispose(revisionListDialogBox);
revisionListDialogBox = null;
if(FolderCommandStatus.STATUS_CANCELED == revisionListCtr.getStatus()) {
//don't want to delete revisions, clean the temporary file
doCancel(ureq);
} else if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) {
doFinishRevisionList(ureq);
}
}
}
/**
* Delete the uploaded file and send cancel event
* @param ureq
*/
private void doCancel(UserRequest ureq) {
if(newFile != null) {
newFile.deleteSilently();
}
fireEvent(ureq, Event.CANCELLED_EVENT);
}
private void doFinishOverwrite(UserRequest ureq) {
if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) {
//new version
int maxNumOfRevisions = getMaxNumOfRevisionsOfExistingVFSItem();
if(maxNumOfRevisions == 0) {
//someone play with the configuration
// Overwrite...
String fileName = existingVFSItem.getName();
existingVFSItem.delete();
newFile.rename(fileName);
// ... and notify listeners.
finishUpload(ureq);
} else {
askForComment(ureq);
}
} else {
//if the file is locked, ask for unlocking it
if(vfsLockManager.isLocked(existingVFSItem)) {
askForUnlock(ureq);
} else {
// Overwrite...
String fileName = existingVFSItem.getName();
existingVFSItem.delete();
newFile.rename(fileName);
// ... and notify listeners.
finishUpload(ureq);
}
}
}
private void doFinishComment(UserRequest ureq) {
String comment = commentVersionCtr.getComment();
Roles roles = ureq.getUserSession().getRoles();
boolean locked = vfsLockManager.isLocked(existingVFSItem);
if(locked && !commentVersionCtr.keepLocked()) {
vfsLockManager.unlock(existingVFSItem, getIdentity(), roles);
}
commentVersionDialogBox.deactivate();
if(revisionListDialogBox != null) {
revisionListDialogBox.deactivate();
}
//ok, new version of the file
Versionable existingVersionableItem = (Versionable)existingVFSItem;
boolean ok = existingVersionableItem.getVersions().addVersion(ureq.getIdentity(), comment, newFile.getInputStream());
if(ok) {
newFile.deleteSilently();
//what can i do if existingVFSItem is a container
if(existingVFSItem instanceof VFSLeaf) {
newFile = (VFSLeaf)existingVFSItem;
}
}
finishUpload(ureq);
}
private void doFinishRevisionList(UserRequest ureq) {
if(existingVFSItem.getParentContainer() != null) {
existingVFSItem = existingVFSItem.getParentContainer().resolve(existingVFSItem.getName());
}
Versionable versionable = (Versionable)existingVFSItem;
Versions versions = versionable.getVersions();
int maxNumOfRevisions = getMaxNumOfRevisionsOfExistingVFSItem();
if(maxNumOfRevisions < 0 || maxNumOfRevisions > versions.getRevisions().size()) {
askForComment(ureq);
} else {
askToReduceRevisionList(ureq, versionable);
}
}
private int getMaxNumOfRevisionsOfExistingVFSItem() {
String relPath = null;
if(existingVFSItem instanceof OlatRootFileImpl) {
relPath = ((OlatRootFileImpl)existingVFSItem).getRelPath();
}
return FolderConfig.versionsAllowed(relPath);
}
private void askToReduceRevisionList(UserRequest ureq, Versionable versionable) {
removeAsListenerAndDispose(revisionListCtr);
removeAsListenerAndDispose(revisionListDialogBox);
Versions versions = versionable.getVersions();
int maxNumOfRevisions = getMaxNumOfRevisionsOfExistingVFSItem();
String[] params = new String[]{ Integer.toString(maxNumOfRevisions), Integer.toString(versions.getRevisions().size()) };
String title = translate("ul.tooManyRevisions.title", params);
String description = translate("ul.tooManyRevisions.description", params);
revisionListCtr = new RevisionListController(ureq, getWindowControl(), versionable, null, description, false);
listenTo(revisionListCtr);
revisionListDialogBox = new CloseableModalController(getWindowControl(), translate("delete"), revisionListCtr.getInitialComponent(), true, title);
listenTo(revisionListDialogBox);
revisionListDialogBox.activate();
}
private void askForUnlock(UserRequest ureq) {
removeAsListenerAndDispose(unlockCtr);
removeAsListenerAndDispose(unlockDialogBox);
unlockCtr = new VersionCommentController(ureq,getWindowControl(), true, false);
listenTo(unlockCtr);
String title = unlockCtr.getAndRemoveFormTitle();
unlockDialogBox = new CloseableModalController(getWindowControl(), translate("ok"), unlockCtr.getInitialComponent(), true, title);
listenTo(unlockDialogBox);
unlockDialogBox.activate();
}
private void askForComment(UserRequest ureq) {
removeAsListenerAndDispose(commentVersionCtr);
removeAsListenerAndDispose(commentVersionDialogBox);
boolean locked = vfsLockManager.isLocked(existingVFSItem);
commentVersionCtr = new VersionCommentController(ureq, getWindowControl(), locked, true);
listenTo(commentVersionCtr);
String title = commentVersionCtr.getAndRemoveFormTitle();
commentVersionDialogBox = new CloseableModalController(getWindowControl(), translate("save"), commentVersionCtr.getInitialComponent(), true, title);
listenTo(commentVersionDialogBox);
commentVersionDialogBox.activate();
}
private void doUpload(UserRequest ureq) {
// check for available space
if (remainingQuotKB != -1 && (fileEl.getUploadFile().length() / 1024 > remainingQuotKB)) {
fileEl.setErrorKey("QuotaExceeded", null);
fileEl.getUploadFile().delete();
return;
}
String fileName = fileEl.getUploadFileName();
if(metaDataCtr != null && StringHelper.containsNonWhitespace(metaDataCtr.getFilename())) {
fileName = metaDataCtr.getFilename();
}
File uploadedFile = fileEl.getUploadFile();
if(resizeImg && fileName != null && imageExtPattern.matcher(fileName.toLowerCase()).find()
&& resizeEl.isSelected(0)) {
String extension = FileUtils.getFileSuffix(fileName);
File imageScaled = new File(uploadedFile.getParentFile(), "scaled_" + uploadedFile.getName() + "." + extension);
if(imageHelper.scaleImage(uploadedFile, extension, imageScaled, 1280, 1280, false) != null) {
//problem happen, special GIF's (see bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6358674)
//don't try to scale if not all ok
uploadedFile = imageScaled;
}
}
// check if such a filename does already exist
existingVFSItem = uploadVFSContainer.resolve(fileName);
if (existingVFSItem == null) {
uploadNewFile(ureq, uploadedFile, fileName);
} else {
// file already exists... upload anyway with new filename and
// in the folder manager status.
// rename file and ask user what to do
if ( ! (existingVFSItem instanceof LocalImpl)) {
throw new AssertException("Can only LocalImpl VFS items, don't know what to do with file of type::" + existingVFSItem.getClass().getCanonicalName());
}
String renamedFilename = VFSManager.rename(uploadVFSContainer, existingVFSItem.getName());
newFile = uploadVFSContainer.createChildLeaf(renamedFilename);
// Copy content to tmp file
boolean success = false;
try(InputStream in = new FileInputStream(uploadedFile);
BufferedOutputStream out = new BufferedOutputStream(newFile.getOutputStream(false))) {
success = FileUtils.copy(in, out);
uploadedFile.delete();
} catch (IOException e) {
success = false;
}
if (success) {
boolean locked = vfsLockManager.isLockedForMe(existingVFSItem, getIdentity(), ureq.getUserSession().getRoles());
if (locked) {
//the file is locked and cannot be overwritten
lockedFileDialog(ureq, renamedFilename);
} else if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) {
uploadVersionedFile(ureq, renamedFilename);
} else {
askOverwriteOrRename(ureq, renamedFilename);
}
} else {
showError("failed");
status = FolderCommandStatus.STATUS_FAILED;
fireEvent(ureq, Event.FAILED_EVENT);
}
}
}
private void lockedFileDialog(UserRequest ureq, String renamedFilename) {
removeAsListenerAndDispose(lockedFileDialog);
String title = translate("ul.lockedFile.title");
String text = translate("ul.lockedFile.text", new String[] { existingVFSItem.getName(), renamedFilename} );
lockedFileDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), title, text,
asList(translate("ul.overwrite.threeoptions.rename", renamedFilename), translate("ul.overwrite.threeoptions.cancel")));
listenTo(lockedFileDialog);
lockedFileDialog.activate();
}
private void uploadVersionedFile(UserRequest ureq, String renamedFilename) {
Versionable versionable = (Versionable)existingVFSItem;
Versions versions = versionable.getVersions();
int maxNumOfRevisions = getMaxNumOfRevisionsOfExistingVFSItem();
if(maxNumOfRevisions == 0) {
//it's possible if someone change the configuration
// let calling method decide what to do.
askOverwriteOrRename(ureq, renamedFilename);
} else if(versions.getRevisions().isEmpty() || maxNumOfRevisions < 0 || maxNumOfRevisions > versions.getRevisions().size()) {
// let calling method decide what to do.
askNewVersionOrRename(ureq, renamedFilename);
} else {
//too many revisions
askToReduceRevisionList(ureq, versionable);
}
}
private void askOverwriteOrRename(UserRequest ureq, String renamedFilename) {
removeAsListenerAndDispose(overwriteDialog);
// let calling method decide what to do.
// for this, we put a list with "existing name" and "new name"
overwriteDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(),
translate("ul.overwrite.threeoptions.title"), translate("ul.overwrite.threeoptions.text", new String[] {existingVFSItem.getName(), renamedFilename} ),
asList(translate("ul.overwrite.threeoptions.overwrite"), translate("ul.overwrite.threeoptions.rename", renamedFilename), translate("ul.overwrite.threeoptions.cancel")));
listenTo(overwriteDialog);
overwriteDialog.activate();
}
private void askNewVersionOrRename(UserRequest ureq, String renamedFilename) {
removeAsListenerAndDispose(overwriteDialog);
overwriteDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(),
translate("ul.overwrite.threeoptions.title"), translate("ul.versionoroverwrite", new String[] {existingVFSItem.getName(), renamedFilename} ),
asList(translate("ul.overwrite.threeoptions.newVersion"), translate("ul.overwrite.threeoptions.rename", renamedFilename), translate("ul.overwrite.threeoptions.cancel")));
listenTo(overwriteDialog);
overwriteDialog.activate();
}
private void uploadNewFile(UserRequest ureq, File uploadedFile, String filename) {
// save file and finish
newFile = uploadVFSContainer.createChildLeaf(filename);
boolean success = true;
if(newFile == null) {
// FXOLAT-409 somehow "createChildLeaf" did not succeed...
// if so, there is alread a error-msg in log (vfsContainer.createChildLeaf)
success = false;
} else {
try(InputStream in = new FileInputStream(uploadedFile);
OutputStream out = newFile.getOutputStream(false)) {
FileUtils.bcopy(in, out, "uploadTmpFileToDestFile");
uploadedFile.delete();
} catch (IOException e) {
success = false;
}
}
if (success) {
String filePath = (uploadRelPath == null ? "" : uploadRelPath + "/") + newFile.getName();
finishSuccessfullUpload(filePath, newFile, ureq);
fileInfoMBean.logUpload(newFile.getSize());
fireEvent(ureq, Event.DONE_EVENT);
} else {
showError("failed");
status = FolderCommandStatus.STATUS_FAILED;
fireEvent(ureq, Event.FAILED_EVENT);
}
}
private void finishUpload(UserRequest ureq) {
// in both cases the upload must be finished and notified with a FolderEvent
String filePath = (uploadRelPath == null ? "" : uploadRelPath + "/") + newFile.getName();
VFSItem item = currentContainer.resolve(filePath);
if(item != null) {
finishSuccessfullUpload(filePath, item, ureq);
fileInfoMBean.logUpload(newFile.getSize());
} else {
logWarn("Upload with error:" + filePath, null);
}
fireEvent(ureq, Event.DONE_EVENT);
}
/**
* Internal helper to finish the upload and add metadata
*/
private void finishSuccessfullUpload(String filePath, VFSItem item, UserRequest ureq) {
if (item instanceof OlatRootFileImpl) {
OlatRootFileImpl relPathItem = (OlatRootFileImpl) item;
// create meta data
MetaInfo meta = metaInfoFactory.createMetaInfoFor(relPathItem);
if (metaDataCtr != null) {
meta = metaDataCtr.getMetaInfo(meta);
}
meta.setAuthor(getIdentity());
meta.clearThumbnails();//if overwrite an older file
meta.write();
}
if(item == null) {
logError("File cannot be uploaded: " + filePath, null);
} else {
ThreadLocalUserActivityLogger.log(FolderLoggingAction.FILE_UPLOADED, getClass(), CoreLoggingResourceable.wrapUploadFile(filePath));
// Notify listeners about upload
fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, item));
}
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#doDispose()
*/
@Override
protected void doDispose() {
//
}
/**
* @return The uploaded file or NULL if nothing uploaded
*/
public VFSLeaf getUploadedFile(){
return newFile;
}
/**
* @return The upload status
*/
public int getStatus() {
return status;
}
/**
* @return true: an existing file has benn overwritten; false: no file with
* same name existed or new file has been renamed
*/
public boolean isExistingFileOverwritten() {
return fileOverwritten;
}
/**
* Set the max upload limit.
* @param uploadLimitKB
*/
public void setMaxUploadSizeKB(long uploadLimitKB) {
this.uploadLimitKB = uploadLimitKB;
String supportAddr = WebappHelper.getMailConfig("mailQuota");
fileEl.setMaxUploadSizeKB(uploadLimitKB, "ULLimitExceeded", new String[] { Formatter.roundToString((uploadLimitKB+0f) / 1000, 1), supportAddr });
}
/**
* Reset the upload controller
*/
public void reset() {
newFile = null;
existingVFSItem = null;
status = FolderCommandStatus.STATUS_SUCCESS;
fileEl.reset();
}
/**
* Call this to remove the fieldset and title from the form rendering. This
* can not be reverted. Default is to show the upload title and fieldset,
* after calling this function no more title will be shown.
*/
public void hideTitleAndFieldset() {
this.setFormTitle(null);
}
/**
* Set the relative path within the rootDir where uploaded files should be put
* into. If NULL, the root Dir is used
*
* @param uploadRelPath
*/
public void setUploadRelPath(String uploadRelPath) {
this.uploadRelPath = uploadRelPath;
// Set upload directory from path
uploadVFSContainer = VFSManager.resolveOrCreateContainerFromPath(currentContainer, uploadRelPath);
if (uploadVFSContainer == null) {
logError("Can not create upload rel path::" + uploadRelPath + ", fall back to current container", null);
uploadVFSContainer = currentContainer;
}
// Update the destination path in the GUI
if (showTargetPath) {
String path = "/ " + currentContainer.getName() + (uploadRelPath == null ? "" : " / " + uploadRelPath);
VFSContainer container = currentContainer.getParentContainer();
while (container != null) {
path = "/ " + container.getName() + " " + path;
container = container.getParentContainer();
}
pathEl.setValue(path);
}
}
public String getNewFileName() {
return (this.newFile != null) ? this.newFile.getName() : null;
}
@Override
protected boolean validateFormLogic(UserRequest ureq) {
// Check sub path
if (targetSubPath != null) {
String subPath = targetSubPath.getValue();
if (subPath != null) {
// Cleanup first
subPath = subPath.toLowerCase().trim();
if (!validSubPathPattern.matcher(subPath).matches()) {
targetSubPath.setErrorKey("subpath.error.characters", null);
return false;
} else {
// Fix mess with slashes and dots
// reduce doubled slashes with single slash
subPath = subPath.replaceAll("\\.*\\/+\\.*", "\\/");
// do it a second time to catch the double slashes created by previous replacement
subPath = subPath.replaceAll("\\/+", "\\/");
// remove slash at end
if (subPath.endsWith("/")) {
subPath = subPath.substring(0, subPath.length()-1);
}
// single slash means no sub-directory
if (subPath.length() == 1 && subPath.startsWith("/")) {
subPath = "";
}
// fix missing slash at start
if (subPath.length() > 0 && !subPath.startsWith("/")) {
subPath = "/" + subPath;
}
// update in GUI so user sees how we optimized
targetSubPath.setValue(subPath);
}
// Now check if this path does not already exist
if (StringHelper.containsNonWhitespace(subPath)){
// Try to resolve given rel path from current container
VFSItem uploadDir = currentContainer.resolve(subPath);
if (uploadDir != null) {
// already exists. this is fine, as long as it is a directory and not a file
if (!(uploadDir instanceof VFSContainer)) {
// error
targetSubPath.setErrorKey("subpath.error.dir.is.file", new String[] {subPath});
return false;
}
}
}
targetSubPath.clearError();
}
}
// Check file name
if(metaDataCtr != null && StringHelper.containsNonWhitespace(metaDataCtr.getFilename())) {
return validateFilename(metaDataCtr.getFilename(), metaDataCtr.getFilenameEl());
}
boolean allOk = validateFilename(fileEl);
return allOk;
}
private boolean validateFilename(FileElement itemEl) {
boolean allOk = true;
// validate clean the errors
List<ValidationStatus> fileStatus = new ArrayList<>();
// revalidate
itemEl.validate(fileStatus);
if(fileStatus.isEmpty()) {
String filename = itemEl.getUploadFileName();
if (!StringHelper.containsNonWhitespace(filename)) {
itemEl.setErrorKey("NoFileChosen", null);
allOk &= false;
}
if(uriValidation) {
try {
new URI(filename);
} catch(Exception e) {
itemEl.setErrorKey("cfile.name.notvalid.uri", null);
allOk &= false;
}
}
if(!FileUtils.validateFilename(filename)) {
itemEl.setErrorKey("cfile.name.notvalid", null);
allOk &= false;
}
allOk &= validateQuota(itemEl);
}
itemEl.setDeleteEnabled(!allOk);
return allOk;
}
private boolean validateFilename(String filename, FormItem itemEl) {
itemEl.clearError();
boolean allOk = true;
if (!StringHelper.containsNonWhitespace(filename)) {
itemEl.setErrorKey("NoFileChosen", null);
allOk &= false;
}
if(uriValidation) {
try {
new URI(filename);
} catch(Exception e) {
itemEl.setErrorKey("cfile.name.notvalid.uri", null);
allOk &= false;
}
}
if(!FileUtils.validateFilename(filename)) {
itemEl.setErrorKey("cfile.name.notvalid", null);
allOk &= false;
}
allOk &= validateQuota(fileEl);
return allOk;
}
private boolean validateQuota(FileElement itemEl) {
if (remainingQuotKB != -1 && itemEl.getUploadFile() != null
&& itemEl.getUploadFile().length() / 1024 > remainingQuotKB) {
String supportAddr = WebappHelper.getMailConfig("mailQuota");
getWindowControl().setError(translate("ULLimitExceeded", new String[] { Formatter.roundToString((uploadLimitKB+0f) / 1000, 1), supportAddr }));
return false;
}
return true;
}
}