All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yadaframework.components.YadaFileManager Maven / Gradle / Ivy

There is a newer version: 0.7.7.R4
Show newest version
package net.yadaframework.components;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import net.yadaframework.core.YadaConfiguration;
import net.yadaframework.exceptions.YadaInvalidUsageException;
import net.yadaframework.persistence.entity.YadaAttachedFile;
import net.yadaframework.persistence.entity.YadaManagedFile;
import net.yadaframework.persistence.repository.YadaAttachedFileDao;
import net.yadaframework.persistence.repository.YadaFileManagerDao;
import net.yadaframework.raw.YadaIntDimension;

/**
 * The File Manager handles uploaded files. They are kept in a specific folder where they can be
 * chosen to be attached to entities.
 *
 */
// Not in YadaWebCMS because used by YadaSession and YadaUtil
@Service
public class YadaFileManager {
	private final transient Logger log = LoggerFactory.getLogger(this.getClass());

    // Use @Lazy to resolve circular reference error
	@Autowired @Lazy private YadaAttachedFileDao yadaAttachedFileDao;
	@Autowired private YadaConfiguration config;
	@Autowired private YadaUtil yadaUtil;
	// Use @Lazy to resolve circular reference error
	@Autowired @Lazy private YadaWebUtil yadaWebUtil;
	@Autowired private YadaFileManagerDao yadaFileManagerDao;
	
	protected String COUNTER_SEPARATOR="_";

	// Image to return when no image is available
	public final static String NOIMAGE_DATA="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA6AAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzEzOCA3OS4xNTk4MjQsIDIwMTYvMDkvMTQtMDE6MDk6MDEgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE3IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkM0MzIwODI4RDk5ODExRTc5NkJEQUU4MkU3NzAwMUNEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkM0MzIwODI5RDk5ODExRTc5NkJEQUU4MkU3NzAwMUNEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RDQyMjJGN0ZEOTBBMTFFNzk2QkRBRTgyRTc3MDAxQ0QiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RDQyMjJGODBEOTBBMTFFNzk2QkRBRTgyRTc3MDAxQ0QiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAhQWRvYmUAZMAAAAABAwAQAwMGCQAAJ/IAADkCAABIi//bAIQABwUFBQUFBwUFBwoGBQYKCwgHBwgLDQsLCwsLDREMDAwMDAwRDQ8QERAPDRQUFhYUFB0dHR0dICAgICAgICAgIAEHCAgNDA0ZEREZHBYSFhwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg/8IAEQgDwAPAAwERAAIRAQMRAf/EANQAAQADAQEBAQEAAAAAAAAAAAAFBgcEAwIBCAEBAAAAAAAAAAAAAAAAAAAAABAAAQQBAwQCAgICAgMAAAAAAwECBAUAEzQVECAzFBESYDJQITEiMCWQwNARAAECAgUICAQFBAIDAQAAAAECAwARECExcRIgQbHRIjJyM1GBkcHhQqITYaGSBGBSgiNzMFCyFGJDkMDw8RIBAAAAAAAAAAAAAAAAAAAA0BMBAAEBBgYCAwEBAAMBAAAAAREAEPAhMUFRIGGBkaGxccEwYNFQ4ZDA0PH/2gAMAwEAAhEDEQAAAP6RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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";

	// TODO distinguere tra mobile portrait e mobile landscape
	// TODO le dimensioni mobile/desktop devono essere configurabili
	// TODO mantenere l'immagine caricata nella versione originale

	/**
	 * Move the file to the public temp folder for later processing.
	 * @param yadaManagedFile
	 * @return
	 * @throws IOException
	 */
	public YadaManagedFile moveToTemp(YadaManagedFile yadaManagedFile) throws IOException {
		File destinationFile = new File(config.getTempImageDir(), yadaManagedFile.getFilename());
		destinationFile = YadaUtil.findAvailableName(destinationFile, null);
		return yadaManagedFile.move(destinationFile);
	}

	/**
	 * Remove a managed file from disk and database
	 * @param managedFile
	 * @return true when deleted from disk, false when not deleted from disk (could have been deleted from db though)
	 */
	public boolean delete(YadaManagedFile managedFile) {
		return yadaFileManagerDao.delete(managedFile);
	}

	/**
	 * Returns the absolute path of a managed file
	 * @param yadaAttachedFile the attachment
	 * @param filename the relative file name, can be yadaAttachedFile.getFilename(), yadaAttachedFile.getFilenameDesktop(), yadaAttachedFile.getFilenameMobile()
	 * @return the File or null
	 */
	public File getAbsoluteFile(YadaManagedFile managedFile) {
		if (managedFile==null) {
			return null;
		}
		return managedFile.getAbsoluteFile();
	}

//	/**
//	 * Deletes a file from disk and database
//	 * @param managedFile the file to delete
//	 */
//	public void delete(YadaManagedFile managedFile) {
//		yadaFileManagerDao.delete(managedFile);
//	}
	
//	private YadaAttachedFile moveFolderByPattern(YadaAttachedFile toMove, List fileRenames) {
//		if (fileRenames!=null) {
//			String relativeFolderPath = toMove.getRelativeFolderPath();
//			for (YadaPatternAndReplace yadaPatternAndReplace : fileRenames) {
//				Matcher matcher = yadaPatternAndReplace.getMatcher(relativeFolderPath);
//				if (matcher.find()) {
//					String newRelativeFolderPath = matcher.replaceAll(yadaPatternAndReplace.getReplace());
//					toMove.setRelativeFolderPath(newRelativeFolderPath);
//					break; // Stop at the first replacement
//				}
//			}
//		}
//		return toMove;
//	}

	/**
	 * You don't usually need to call this method but {@link YadaUtil#copyEntity(net.yadaframework.core.CloneableFiltered)} instead.
* Makes a copy of just the filesystem files. New names are generated from the old ones by appending an incremental number. * The input YadaAttachedFile is updated with the new names. The old files are not deleted. * This method is used by YadaUtil.copyEntity() when a field is a YadaAttachedFile so that files are copied too. * @param yadaAttachedFileCopy a copy of some other YadaAttachedFile that will be left unchanged * @param yadaAttachedFileCloneSet when not null, all files are copied to a temp folder. * This is useful when the final path depends on the id * of a cloned object so it can't be determined during cloning. * The method yadaAttachedFileCloneSet.moveAll() will have to be called after the clone has been persisted. * @return the saved YadaAttachedFile with new files on disk, eventually in a different folder * @throws IOException * @see {@link YadaUtil#copyEntity(net.yadaframework.core.CloneableFiltered)} */ public YadaAttachedFile duplicateFiles(YadaAttachedFile yadaAttachedFileCopy, YadaAttachedFileCloneSet yadaAttachedFileCloneSet) throws IOException { if (yadaAttachedFileCopy==null) { return null; } File sourceFileMobile = getAbsoluteMobileFile(yadaAttachedFileCopy); File sourceFileDesktop = getAbsoluteDesktopFile(yadaAttachedFileCopy); File sourceFilePdf = getAbsolutePdfFile(yadaAttachedFileCopy); File sourceFile = getAbsoluteFile(yadaAttachedFileCopy); // Sometimes sourceFileDesktop and sourceFile have the same value: don't copy the file twice! boolean deskSameAsDefault = sourceFileDesktop!=null && sourceFile!=null && sourceFileDesktop.getAbsolutePath().equals(sourceFile.getAbsolutePath()); if (yadaAttachedFileCloneSet!=null) { yadaAttachedFileCloneSet.handle(yadaAttachedFileCopy); // Moved to temp folder } if (sourceFileMobile!=null) { File targetFileMobile = getAbsoluteMobileFile(yadaAttachedFileCopy); targetFileMobile = YadaUtil.findAvailableName(targetFileMobile, null); try (InputStream inputStream = new FileInputStream(sourceFileMobile); OutputStream outputStream = new FileOutputStream(targetFileMobile)) { IOUtils.copy(inputStream, outputStream); } yadaAttachedFileCopy.setFilenameMobile(targetFileMobile.getName()); } File targetFileDesktop = null; if (sourceFileDesktop!=null) { targetFileDesktop = getAbsoluteDesktopFile(yadaAttachedFileCopy); targetFileDesktop = YadaUtil.findAvailableName(targetFileDesktop, null); try (InputStream inputStream = new FileInputStream(sourceFileDesktop); OutputStream outputStream = new FileOutputStream(targetFileDesktop)) { IOUtils.copy(inputStream, outputStream); } yadaAttachedFileCopy.setFilenameDesktop(targetFileDesktop.getName()); } if (sourceFilePdf!=null) { File targetFilePdf = getAbsolutePdfFile(yadaAttachedFileCopy); targetFilePdf = YadaUtil.findAvailableName(targetFilePdf, null); try (InputStream inputStream = new FileInputStream(sourceFilePdf); OutputStream outputStream = new FileOutputStream(targetFilePdf)) { IOUtils.copy(inputStream, outputStream); } yadaAttachedFileCopy.setFilenamePdf(targetFilePdf.getName()); } if (sourceFile!=null && !deskSameAsDefault) { File targetFile = getAbsoluteFile(yadaAttachedFileCopy); targetFile = YadaUtil.findAvailableName(targetFile, null); try (InputStream inputStream = new FileInputStream(sourceFile); OutputStream outputStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outputStream); } yadaAttachedFileCopy.setFilename(targetFile.getName()); } if (deskSameAsDefault && targetFileDesktop!=null) { // Default file is the same as desktop file yadaAttachedFileCopy.setFilename(targetFileDesktop.getName()); } return yadaAttachedFileDao.save(yadaAttachedFileCopy); } /** * Returns the absolute path of the mobile file * @param yadaAttachedFile the attachment * @return the File or null */ public File getAbsoluteMobileFile(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile!=null) { return getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenameMobile()); } return null; } /** * Returns the absolute path of the desktop file * @param yadaAttachedFile the attachment * @return the File or null */ public File getAbsoluteDesktopFile(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile!=null) { return getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenameDesktop()); } return null; } /** * Returns the absolute path of the pdf file * @param yadaAttachedFile the attachment * @return the File or null */ public File getAbsolutePdfFile(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile!=null) { return getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenamePdf()); } return null; } /** * Returns the absolute path of the default file (no mobile/desktop variant) * @param yadaAttachedFile the attachment * @return the File or null */ public File getAbsoluteFile(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile!=null) { return getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilename()); } return null; } /** * Returns the absolute path of a file * @param yadaAttachedFile the attachment * @param filename the relative file name, can be yadaAttachedFile.getFilename(), yadaAttachedFile.getFilenameDesktop(), yadaAttachedFile.getFilenameMobile() * @return the File or null */ public File getAbsoluteFile(YadaAttachedFile yadaAttachedFile, String filename) { if (filename==null || yadaAttachedFile==null) { return null; } File targetFolder = new File(config.getContentPath(), yadaAttachedFile.getRelativeFolderPath()); return new File(targetFolder, filename); } // /** // * Find one from repository // * @param yadaAttachedFileId the attachment id // */ // public YadaAttachedFile findOne(Long yadaAttachedFileId) { // return yadaAttachedFileRepository.findOne(yadaAttachedFileId); // } /** * Deletes from the filesystem all files related to the attachment * @param yadaAttachedFileId the attachment id * @see #deleteFileAttachment(YadaAttachedFile) */ @Deprecated // Removed in yada 0.7.0 because it uses a Spring Data api (?) public void deleteFileAttachment(Long yadaAttachedFileId) { deleteFileAttachment(yadaAttachedFileDao.findById(yadaAttachedFileId).orElse(null)); } /** * Deletes from the filesystem all files related to the attachment * @param yadaAttachedFile the attachment * @see #deleteFileAttachment(Long) */ public void deleteFileAttachment(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile==null) { return; } if (yadaAttachedFile.getFilename() != null) { getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilename()).delete(); } if (yadaAttachedFile.getFilenamePdf() != null) { getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenamePdf()).delete(); } if (yadaAttachedFile.getFilenameDesktop() != null) { getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenameDesktop()).delete(); } if (yadaAttachedFile.getFilenameMobile() != null) { getAbsoluteFile(yadaAttachedFile, yadaAttachedFile.getFilenameMobile()).delete(); } } /** * Deletes from the filesystem all files related to the attachments * @param yadaAttachedFiles the attachments */ public void deleteFileAttachment(List yadaAttachedFiles) { for (YadaAttachedFile yadaAttachedFile : yadaAttachedFiles) { deleteFileAttachment(yadaAttachedFile); } } /** * Returns the (relative) url of the mobile image if any, or null * @param yadaAttachedFile * @return */ public String getMobileImageUrl(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile==null) { return NOIMAGE_DATA; } String imageName = yadaAttachedFile.getFilenameMobile(); if (imageName==null) { return NOIMAGE_DATA; } return computeUrl(yadaAttachedFile, imageName); } /** * Returns the (relative) url of the desktop image. If not defined, falls back to the plain file. * @param yadaAttachedFile * @return */ public String getDesktopImageUrl(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile==null) { return NOIMAGE_DATA; } String imageName = yadaAttachedFile.getFilenameDesktop(); if (imageName==null) { return getFileUrl(yadaAttachedFile); } return computeUrl(yadaAttachedFile, imageName); } /** * Returns the (relative) url of the pdf image if any, or null. * @param yadaAttachedFile * @return */ public String getPdfImageUrl(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile==null) { return NOIMAGE_DATA; } String imageName = yadaAttachedFile.getFilenamePdf(); if (imageName==null) { return NOIMAGE_DATA; } return computeUrl(yadaAttachedFile, imageName); } /** * Returns the (relative) url of the file, or null. * @param yadaAttachedFile * @return */ public String getFileUrl(YadaAttachedFile yadaAttachedFile) { if (yadaAttachedFile==null) { return null; } String fileName = yadaAttachedFile.getFilename(); if (fileName==null) { return null; } return computeUrl(yadaAttachedFile, fileName); } private String computeUrl(YadaAttachedFile yadaAttachedFile, String filename) { return getFileUrl(yadaAttachedFile.getRelativeFolderPath(), filename); } /** * Returns the url of a YadaAttachedFile that has the given attributes * @param relativeFolderPath * @param filename * @return The URL as a String */ public String getFileUrl(String relativeFolderPath, String filename) { StringBuilder result = new StringBuilder(config.getContentUrl()); result.append(relativeFolderPath) .append("/") .append(filename); return result.toString(); } /** * Uploads a file into the uploads folder. * @param multipartFile * @return * @throws IOException */ private File uploadFileInternal(MultipartFile multipartFile) throws IOException { String originalFilename = multipartFile.getOriginalFilename(); String targetName = yadaUtil.ensureSafeFilename(originalFilename); String[] filenameParts = YadaUtil.splitFileNameAndExtension(targetName); File targetFolder = config.getUploadsFolder(); // Useless: doesn't throw an exception when it fails: targetFolder.mkdirs(); File targetFile = YadaUtil.findAvailableName(new File(targetFolder.getAbsolutePath()), filenameParts[0], filenameParts[1], COUNTER_SEPARATOR); multipartFile.transferTo(targetFile); // try (InputStream inputStream = multipartFile.getInputStream(); OutputStream outputStream = new FileOutputStream(targetFile)) { // IOUtils.copy(inputStream, outputStream); // } catch (IOException e) { // throw e; // } log.debug("File {} uploaded", targetFile.getAbsolutePath()); return targetFile; } /** * Copies a received file to the upload folder. The returned File is the only pointer to the uploaded file. * @param multipartFile file coming from the http request * @return the uploaded file with a unique name, or null if the user did not send any file * @throws IOException */ public File uploadFile(MultipartFile multipartFile) throws IOException { if (yadaWebUtil.isMultipartMissing(multipartFile)) { log.debug("No file sent for upload"); return null; } if (log.isDebugEnabled()) { if (multipartFile.getSize()==0) { log.debug("Empty file sent for upload: {}", multipartFile.getName()); // We still upload it } } File targetFile = uploadFileInternal(multipartFile); return targetFile; } /** * Copies a received file to the upload folder. A pointer to the file is stored in the database as YadaManagedFile * @param multipartFile file coming from the http request * @return the uploaded file with a unique name, or null if the user did not send any file * @throws IOException */ public YadaManagedFile manageFile(MultipartFile multipartFile) throws IOException { return manageFile(multipartFile, null); } /** * Copies a received file to the upload folder. A pointer to the file is stored in the database as YadaManagedFile * @param multipartFile file coming from the http request * @param description a user description for the file * @return the uploaded file with a unique name, or null if the user did not send any file * @throws IOException */ public YadaManagedFile manageFile(MultipartFile multipartFile, String description) throws IOException { if (multipartFile==null || multipartFile.getSize()==0) { log.debug("No file sent for upload"); return null; } File targetFile = uploadFileInternal(multipartFile); YadaManagedFile yadaManagedFile = yadaFileManagerDao.createManagedFile(multipartFile, targetFile, description); return yadaManagedFile; } /** * Replace the file associated with the current attachment * The multipartFile is moved to the destination when config.isFileManagerDeletingUploads() is true, otherwise the original is copied * and left unchanged. * @param currentAttachedFile an existing attachment, never null * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not changed. * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException */ public YadaAttachedFile attachReplace(YadaAttachedFile currentAttachedFile, MultipartFile multipartFile, String namePrefix) throws IOException { File managedFile = uploadFile(multipartFile); return attachReplace(currentAttachedFile, managedFile, multipartFile, namePrefix); } /** * Replace the file associated with the current attachment * The managedFile is moved to the destination when config.isFileManagerDeletingUploads() is true, otherwise the original is copied * and left unchanged. * @param currentAttachedFile an existing attachment, never null * @param managedFile the new file to set * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not changed. * @return YadaAttachedFile if the file has been replaced, null if managedFile is null * @throws IOException */ public YadaAttachedFile attachReplace(YadaAttachedFile currentAttachedFile, File managedFile, MultipartFile multipartFile, String namePrefix) throws IOException { return attachReplace(currentAttachedFile, managedFile, multipartFile, namePrefix, null, null, null); } /** * Replace the file associated with the current attachment, only if a file was actually attached * The managedFile is moved to the destination when config.isFileManagerDeletingUploads() is true, otherwise the original is copied * and left unchanged. * @param currentAttachedFile an existing attachment, never null * @param managedFile the new file to set * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not changed. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return YadaAttachedFile if the file has been replaced, null if managedFile is null * @throws IOException */ public YadaAttachedFile attachReplace(YadaAttachedFile currentAttachedFile, File managedFile, MultipartFile multipartFile, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { boolean needToDeleteOriginal = config.isFileManagerDeletingUploads(); return attachReplace(needToDeleteOriginal, currentAttachedFile, managedFile, multipartFile, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Replace the file associated with the current attachment, only if a file was actually attached * @param move the managedFile is moved to the destination when true, otherwise the original is copied * and left unchanged. * @param currentAttachedFile an existing attachment, never null * @param managedFile the new file to set * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not changed. * @param namePrefix optional prefix to set before the original file name. Add a separator if you need one. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return YadaAttachedFile if the file has been replaced, null if managedFile is null * @throws IOException */ public YadaAttachedFile attachReplace(boolean move, YadaAttachedFile currentAttachedFile, File managedFile, MultipartFile multipartFile, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { if (managedFile==null) { return null; } if (currentAttachedFile==null) { // We can't call attachNew() instead, because we don't have relativeFolderPath here throw new YadaInvalidUsageException("currentAttachedFile is missing"); } deleteFileAttachment(currentAttachedFile); // Delete any previous attached files String clientFilename = null; if (multipartFile!=null) { clientFilename = multipartFile.getOriginalFilename(); } return attach(move, currentAttachedFile, managedFile, clientFilename, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Copies an uploaded file to the destination folder, creating a database association to assign to an Entity. * The name of the file is in the format [basename]managedFileName_id.ext. * Images are not resized. * @param multipartFile the original uploaded file * @param relativeFolderPath path of the target folder relative to the contents folder, starting with a slash / * @param namePrefix prefix to attach before the original file name. Add a separator if you need one. Can be null. * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException * @see {@link #attach(File, String, String, String, Integer, Integer)} */ public YadaAttachedFile attachNew(MultipartFile multipartFile, String relativeFolderPath, String namePrefix) throws IOException { File managedFile = uploadFile(multipartFile); return attachNew(managedFile, multipartFile, relativeFolderPath, namePrefix, null, null, null); } /** * Copies a managed file to the destination folder, creating a database association to assign to an Entity. * The name of the file is in the format [basename]managedFileName_id.ext. * Images are not resized. * @param managedFile an uploaded file, can be an image or not * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not set. * @param relativeFolderPath path of the target folder relative to the contents folder * @param namePrefix optional prefix to set before the original file name. Add a separator if you need one. * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException * @see {@link #attach(File, String, String, String, Integer, Integer)} */ public YadaAttachedFile attachNew(File managedFile, MultipartFile multipartFile, String relativeFolderPath, String namePrefix) throws IOException { return attachNew(managedFile, multipartFile, relativeFolderPath, namePrefix, null, null, null); } /** * Copies (and resizes) a managed file to the destination folder, creating a database association to assign to an Entity. * The name of the file is in the format [basename]managedFileName_id.ext * @param managedFile an uploaded file, can be an image or not. When null, nothing is done. * @param multipartFile the original uploaded file, to get the client filename. If null, the client filename is not changed. * @param relativeFolderPath path of the target folder relative to the contents folder, starting with a slash / * @param namePrefix prefix to attach before the original file name. Add a separator if you need one. Can be null. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException * @see {@link #attach(File, String, String, String)} */ public YadaAttachedFile attachNew(File managedFile, MultipartFile multipartFile, String relativeFolderPath, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { String clientFilename = null; if (multipartFile!=null) { clientFilename = multipartFile.getOriginalFilename(); } return attachNew(managedFile, clientFilename, relativeFolderPath, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Copies (and resizes) a managed file to the destination folder, creating a database association to assign to an Entity. * The name of the file is in the format [basename]managedFileName_id.ext * @param managedFile an uploaded file, can be an image or not. When null, nothing is done. * @param clientFilename the original client filename. If null, the client filename is not changed. * @param relativeFolderPath path of the target folder relative to the contents folder, starting with a slash / * @param namePrefix prefix to attach before the original file name. Add a separator if you need one. Can be null. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException * @see {@link #attach(File, String, String, String)} */ public YadaAttachedFile attachNew(File managedFile, String clientFilename, String relativeFolderPath, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { boolean needToDeleteOriginal = config.isFileManagerDeletingUploads(); return attachNew(needToDeleteOriginal, managedFile, clientFilename, relativeFolderPath, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Copies (and resizes) a managed file to the destination folder, creating a database association to assign to an Entity. * The name of the file is in the format [basename]managedFileName_id.ext * @param move true if the original file has to be deleted (moved when not transformed), false to keep it there * @param managedFile an uploaded file, can be an image or not. When null, nothing is done. * @param clientFilename the original client filename. If null, the client filename is not changed. * @param relativeFolderPath path of the target folder relative to the contents folder, starting with a slash / * @param namePrefix prefix to attach before the original file name. Add a separator if you need one. Can be null. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return YadaAttachedFile if the file is uploaded, null if no file was sent by the user * @throws IOException * @see {@link #attach(File, String, String, String)} */ public YadaAttachedFile attachNew(boolean move, File managedFile, String clientFilename, String relativeFolderPath, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { if (managedFile==null) { return null; } if (!relativeFolderPath.startsWith("/") && !relativeFolderPath.startsWith("\\")) { relativeFolderPath = "/" + relativeFolderPath; log.warn("The relativeFolderPath '{}' should have a leading slash (fixed)", relativeFolderPath); } YadaAttachedFile yadaAttachedFile = new YadaAttachedFile(); // yadaAttachedFile.setAttachedToId(attachToId); yadaAttachedFile.setRelativeFolderPath(relativeFolderPath); // This save should not bee needed anymore because of @PostPersist in YadaAttachedFile yadaAttachedFile = yadaAttachedFileDao.save(yadaAttachedFile); // Get the id File targetFolder = new File(config.getContentPath(), relativeFolderPath); targetFolder.mkdirs(); return attach(move, yadaAttachedFile, managedFile, clientFilename, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Performs file copy and (for images) resize to different versions. * The managedFile is moved to the destination when config.isFileManagerDeletingUploads() is true, otherwise the original is copied * and left unchanged. * @param yadaAttachedFile object to fill with values * @param managedFile some file to attach or replace, can be an image or not. When null, nothing is done. * @param clientFilename the client filename. If null, the client filename is not changed. * @param namePrefix prefix to attach before the original file name to make the target name. Add a separator (like a dash) if you need one. Can be null. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return * @throws IOException */ private YadaAttachedFile attach(YadaAttachedFile yadaAttachedFile, File managedFile, String clientFilename, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { boolean needToDeleteOriginal = config.isFileManagerDeletingUploads(); return attach(needToDeleteOriginal, yadaAttachedFile, managedFile, clientFilename, namePrefix, targetExtension, desktopWidth, mobileWidth); } /** * Performs file copy and (for images) resize to different versions. * The managedFile is moved to the destination when config.isFileManagerDeletingUploads() is true, otherwise the original is copied * and left unchanged. * @param move true if the original file has to be deleted (moved when not transformed), false to keep it there * @param yadaAttachedFile object to fill with values * @param managedFile some file to attach or replace, can be an image or not. When null, nothing is done. * @param clientFilename the client filename. If null, the client filename is not changed. * @param namePrefix prefix to attach before the original file name to make the target name. Add a separator (like a dash) if you need one. Can be null. * @param targetExtension optional, to convert image file formats * @param desktopWidth optional width for desktop images - when null, the image is not resized * @param mobileWidth optional width for mobile images - when null, the mobile file is the same as the desktop * @return * @throws IOException */ public YadaAttachedFile attach(boolean move, YadaAttachedFile yadaAttachedFile, File managedFile, String clientFilename, String namePrefix, String targetExtension, Integer desktopWidth, Integer mobileWidth) throws IOException { // yadaAttachedFile.setUploadTimestamp(new Date()); if (clientFilename!=null) { yadaAttachedFile.setClientFilename(clientFilename); } String origExtension = yadaUtil.getFileExtension(yadaAttachedFile.getClientFilename()); if (targetExtension==null) { targetExtension = origExtension; } YadaIntDimension dimension = yadaUtil.getImageDimension(managedFile); yadaAttachedFile.setImageDimension(dimension); boolean imageExtensionChanged = (origExtension==null && targetExtension!=null) || (targetExtension!=null && targetExtension.compareToIgnoreCase(origExtension)!=0); boolean requiresTransofmation = imageExtensionChanged || desktopWidth!=null || mobileWidth!=null; // // If the file does not need resizing, there is just one default filename like "product-mydoc_2631.pdf" if (!requiresTransofmation) { File targetFile = yadaAttachedFile.calcAndSetTargetFile(namePrefix, targetExtension, null, YadaAttachedFile.YadaAttachedFileType.DEFAULT); // File targetFile = new File(targetFolder, targetFilenamePrefix + "." + targetExtension); if (move) { // Just move the old file to the new destination Files.move(managedFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } else { // Copy bytes try (InputStream inputStream = new FileInputStream(managedFile); OutputStream outputStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outputStream); } catch (IOException e) { throw e; } } } else { // Transformation: copy with imagemagick // If desktopWidth is null, the image original size does not change. // The file name is like "product-mydoc_2631_640.jpg" File targetFile = yadaAttachedFile.calcAndSetTargetFile(namePrefix, targetExtension, desktopWidth, YadaAttachedFile.YadaAttachedFileType.DESKTOP); resizeAndConvertImageAsNeeded(managedFile, targetFile, desktopWidth); yadaAttachedFile.setFilename(targetFile.getName()); if (mobileWidth==null) { yadaAttachedFile.setFilenameMobile(null); // No mobile image } else { targetFile = yadaAttachedFile.calcAndSetTargetFile(namePrefix, targetExtension, mobileWidth, YadaAttachedFile.YadaAttachedFileType.MOBILE); resizeAndConvertImageAsNeeded(managedFile, targetFile, mobileWidth); } if (move) { log.debug("Deleting original file {}", managedFile.getAbsolutePath()); managedFile.delete(); } } return yadaAttachedFileDao.save(yadaAttachedFile); } /** * Perform image format conversion and/or resize, when needed * @param sourceFile * @param targetFile * @param targetWidth resize width, can be null for no resize */ private void resizeAndConvertImageAsNeeded(File sourceFile, File targetFile, Integer targetWidth) { if (targetWidth==null) { // Convert only Map params = new HashMap<>(); params.put("FILENAMEIN", sourceFile.getAbsolutePath()); params.put("FILENAMEOUT", targetFile.getAbsolutePath()); boolean convert = yadaUtil.exec("config/shell/convert", params); if (!convert) { log.error("Image not copied when making attachment: {}", targetFile); } } else { // Resize Map params = new HashMap<>(); params.put("FILENAMEIN", sourceFile.getAbsolutePath()); params.put("FILENAMEOUT", targetFile.getAbsolutePath()); params.put("W", Integer.toString(targetWidth)); params.put("H", ""); // the height must be empty to keep the original proportions and resize based on width boolean resized = yadaUtil.exec("config/shell/resize", params); if (!resized) { log.error("Image not resized when making attachment: {}", targetFile); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy