/*
* Created on 19/giu/2010
*
* Copyright 2010 by Andrea Vacondio (andrea.vacondio@gmail.com).
*
* This file is part of the Sejda source code
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sejda.core.support.io;
import static java.util.Optional.of;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.sejda.core.support.io.IOUtils.findNewNameThatDoesNotExist;
import static org.sejda.core.support.io.IOUtils.unhide;
import static org.sejda.model.output.ExistingOutputPolicy.FAIL;
import static org.sejda.model.output.ExistingOutputPolicy.SKIP;
import java.io.File;
import java.io.FileInputStream;
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.Map;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.sejda.model.output.ExistingOutputPolicy;
import org.sejda.model.task.TaskExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class responsible for writing the input files to the output destination
*
* @author Andrea Vacondio
*
*/
final class OutputWriterHelper {
private static final Logger LOG = LoggerFactory.getLogger(OutputWriterHelper.class);
private OutputWriterHelper() {
// util class
}
/**
* Moves the input file contained in the input map (single file) to the output file
*
* @param files
* @param outputFile
* @param existingOutputPolicy
* policy to use if an output that already exists is found
* @param executionContext
* current execution context
* @throws IOException
*/
static void moveToFile(Map<String, File> files, File outputFile, ExistingOutputPolicy existingOutputPolicy,
TaskExecutionContext executionContext) throws IOException {
if (outputFile.exists() && !outputFile.isFile()) {
throw new IOException(String.format("Wrong output destination %s, must be a file.", outputFile));
}
if (files.size() != 1) {
throw new IOException(
String.format("Wrong files map size %d, must be 1 to copy to the selected destination %s",
files.size(), outputFile));
}
for (Entry<String, File> entry : files.entrySet()) {
moveFile(entry.getValue(), outputFile, of(existingOutputPolicy).filter(p -> p != SKIP).orElseGet(() -> {
LOG.debug("Cannot use {} output policy for single output, replaced with {}", SKIP, FAIL);
return FAIL;
}), executionContext);
}
}
/**
* Moves the input files to the output directory
*
* @param files
* @param outputDirectory
* @param existingOutputPolicy
* policy to use if an output that already exists is found
* @param executionContext
* current execution context
* @throws IOException
*/
static void moveToDirectory(Map<String, File> files, File outputDirectory,
ExistingOutputPolicy existingOutputPolicy, TaskExecutionContext executionContext) throws IOException {
if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
throw new IOException(String.format("Unable to make destination directory tree %s.", outputDirectory));
}
if (!outputDirectory.isDirectory()) {
throw new IOException(String.format("Wrong output destination %s, must be a directory.", outputDirectory));
}
for (Entry<String, File> entry : files.entrySet()) {
if (isBlank(entry.getKey())) {
throw new IOException(String.format(
"Unable to move %s to the output directory, no output name specified.", entry.getValue()));
}
moveFile(entry.getValue(), new File(outputDirectory, entry.getKey()), existingOutputPolicy,
executionContext);
}
}
/**
* Moves the input file to the output file
*
* @param input
* input file
* @param output
* output file
* @param existingOutputPolicy
* policy to use if an output that already exists is found
* @param executionContext
* @throws IOException
*/
static void moveFile(File input, File output, ExistingOutputPolicy existingOutputPolicy,
TaskExecutionContext executionContext) throws IOException {
if (output.exists()) {
switch (existingOutputPolicy) {
case OVERWRITE:
LOG.debug("Moving {} to {}.", input, output);
Files.move(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
unhide(output.toPath());
executionContext.notifiableTaskMetadata().addTaskOutput(output);
break;
case RENAME:
File newNamedOutput = findNewNameThatDoesNotExist(output);
LOG.debug("Output exists {}, will use new name {}.", output, newNamedOutput);
doMoveFile(input, newNamedOutput);
executionContext.notifiableTaskMetadata().addTaskOutput(newNamedOutput);
break;
case SKIP:
LOG.info("Skipping already existing output file {}", output);
break;
default:
throw new IOException(
String.format("Unable to write %s to the already existing file destination %s. (policy is %s)",
input, output, existingOutputPolicy));
}
} else {
LOG.debug("Moving {} to {}.", input, output);
doMoveFile(input, output);
executionContext.notifiableTaskMetadata().addTaskOutput(output);
}
}
private static void doMoveFile(File input, File output) throws IOException {
try {
FileUtils.moveFile(input, output);
unhide(output.toPath());
} catch (IOException ex) {
if (ex.getMessage().contains("Failed to delete original file")) {
// Don't crash the task because we have leftover temp files, just warn
LOG.warn(ex.getMessage());
input.deleteOnExit();
} else {
throw ex;
}
}
}
/**
* Copy the populated file map to a zip output stream
*
* @param files
* @param out
* @throws IOException
*/
static void copyToStreamZipped(Map<String, File> files, OutputStream out) throws IOException {
ZipOutputStream zipOut = new ZipOutputStream(out);
for (Entry<String, File> entry : files.entrySet()) {
FileInputStream input = null;
if (isBlank(entry.getKey())) {
throw new IOException(String.format("Unable to copy %s to the output stream, no output name specified.",
entry.getValue()));
}
try {
input = new FileInputStream(entry.getValue());
zipOut.putNextEntry(new ZipEntry(entry.getKey()));
LOG.debug("Copying {} to zip stream {}.", entry.getValue(), entry.getKey());
IOUtils.copy(input, zipOut);
} finally {
IOUtils.closeQuietly(input);
delete(entry.getValue());
}
}
IOUtils.closeQuietly(zipOut);
}
/**
* Copies the contents of the file to the specified outputstream, without zipping or applying any other changes.
*
* @param file
* @param out
* @throws IOException
*/
static void copyToStream(File file, OutputStream out) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(file);
IOUtils.copy(in, out);
} finally {
IOUtils.closeQuietly(in);
delete(file);
}
}
private static void delete(File file) {
if (!file.delete()) {
LOG.warn("Unable to delete temporary file {}", file);
}
}
}