Coverage Summary for Class: FileManagement (org.kitodo.filemanagement)
Class |
Method, %
|
Line, %
|
FileManagement |
97,4%
(38/39)
|
82,5%
(203/246)
|
FileManagement$1 |
100%
(1/1)
|
100%
(1/1)
|
Total |
97,5%
(39/40)
|
82,6%
(204/247)
|
/*
* (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/
package org.kitodo.filemanagement;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.api.command.CommandInterface;
import org.kitodo.api.filemanagement.FileManagementInterface;
import org.kitodo.api.filemanagement.ProcessSubType;
import org.kitodo.api.filemanagement.filters.FileNameEndsWithFilter;
import org.kitodo.config.KitodoConfig;
import org.kitodo.config.enums.ParameterFileManagement;
import org.kitodo.serviceloader.KitodoServiceLoader;
public class FileManagement implements FileManagementInterface {
private static final Logger logger = LogManager.getLogger(FileManagement.class);
private static final FileMapper fileMapper = new FileMapper();
private static final String IMAGES_DIRECTORY_NAME = "images";
private final CommandInterface commandService = new KitodoServiceLoader<CommandInterface>(CommandInterface.class)
.loadModule();
@Override
public URI create(URI parentFolderUri, String name, boolean file) throws IOException {
if (file) {
return createResource(parentFolderUri, name);
}
return createDirectory(parentFolderUri, name);
}
private URI createDirectory(URI parentFolderUri, String directoryName) throws IOException {
parentFolderUri = fileMapper.mapUriToKitodoDataDirectoryUri(parentFolderUri);
File directory = new File(Paths.get(new File(parentFolderUri).getPath(), directoryName).toUri());
if (!directory.exists() && !directory.mkdir()) {
throw new IOException("Could not create directory: " + directory);
}
return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(directory.getPath()).toUri());
}
private URI createResource(URI targetFolder, String fileName) throws IOException {
targetFolder = fileMapper.mapUriToKitodoDataDirectoryUri(targetFolder);
File file = new File(Paths.get(new File(targetFolder).getPath(), fileName).toUri());
if (file.exists() || file.createNewFile()) {
return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(file.getPath()).toUri());
}
return URI.create("");
}
@Override
public OutputStream write(URI uri) throws IOException {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
return Files.newOutputStream(Paths.get(uri));
}
@Override
public InputStream read(URI uri) throws IOException {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
return uri.toURL().openStream();
}
@Override
public void copy(URI sourceUri, URI targetUri) throws IOException {
sourceUri = fileMapper.mapUriToKitodoDataDirectoryUri(sourceUri);
boolean isDirectory = targetUri.getPath().endsWith("/");
targetUri = fileMapper.mapUriToKitodoDataDirectoryUri(targetUri);
String targetPath = targetUri.getPath();
File targetFile = new File(targetPath);
if (!fileExist(sourceUri)) {
throw new FileNotFoundException();
} else if (isFile(sourceUri) && ((targetFile.exists() && !targetFile.isDirectory()) || !isDirectory)) {
copyFile(new File(sourceUri), new File(targetUri));
} else if (isFile(sourceUri)) {
copyFileToDirectory(new File(sourceUri), new File(targetUri));
} else if (isDirectory(sourceUri)) {
copyDirectory(new File(sourceUri), new File(targetUri));
}
}
private void copyDirectory(File sourceDirectory, File targetDirectory) throws IOException {
if (!targetDirectory.exists()) {
targetDirectory.mkdirs();
}
FileUtils.copyDirectory(sourceDirectory, targetDirectory, false);
}
private void copyFile(File sourceFile, File destinationFile) throws IOException {
FileUtils.copyFile(sourceFile, destinationFile);
}
private void copyFileToDirectory(File sourceFile, File targetDirectory) throws IOException {
FileUtils.copyFileToDirectory(sourceFile, targetDirectory);
}
@Override
public boolean delete(URI uri) throws IOException {
if (Objects.isNull(uri) || uri.getPath().isEmpty()) {
/*
This exception is thrown when the passed URI is empty or null.
Using this URI would cause the deletion of the metadata directory.
*/
throw new IOException("Attempt to delete subdirectory with URI that is empty or null!");
}
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
File file = new File(uri);
if (file.exists()) {
if (file.isFile()) {
return Files.deleteIfExists(file.toPath());
}
if (file.isDirectory()) {
FileUtils.deleteDirectory(file);
return true;
}
return false;
}
return true;
}
@Override
public void move(URI sourceUri, URI targetUri) throws IOException {
copy(sourceUri, targetUri);
delete(sourceUri);
}
@Override
public URI rename(URI uri, String newName) throws IOException {
if (Objects.isNull(uri) || Objects.isNull(newName)) {
return null;
}
if (isDirectory(uri)) {
return renameDirectory(uri, newName);
}
String substring = uri.toString().substring(0, uri.toString().lastIndexOf('/') + 1);
if (newName.contains("/")) {
newName = newName.substring(newName.lastIndexOf('/') + 1);
}
URI newFileUri = URI.create(substring + newName);
URI mappedFileURI = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
URI mappedNewFileURI = fileMapper.mapUriToKitodoDataDirectoryUri(newFileUri);
if (!fileExist(mappedFileURI)) {
logger.debug("File {} does not exist for renaming.", uri.getPath());
throw new FileNotFoundException(uri + " does not exist for renaming.");
}
if (fileExist(mappedNewFileURI)) {
String message = "Renaming of " + uri + " into " + newName + " failed: Destination exists.";
logger.error(message);
throw new IOException(message);
}
return performRename(mappedFileURI, mappedNewFileURI);
}
private URI renameDirectory(URI directoryPath, String newDirectoryName) throws IOException {
File newDirectory = new File(KitodoConfig.getKitodoDataDirectory() + newDirectoryName);
File oldDirectory = new File(KitodoConfig.getKitodoDataDirectory() + directoryPath);
if (!oldDirectory.equals(newDirectory)) {
boolean success = oldDirectory.renameTo(newDirectory);
if (success) {
return newDirectory.toURI();
} else {
throw new IOException("Unable to rename directory '" + directoryPath + "'!");
}
} else {
return newDirectory.toURI();
}
}
private URI performRename(URI mappedFileURI, URI mappedNewFileURI) throws IOException {
File fileToRename = new File(mappedFileURI);
File renamedFile = new File(mappedNewFileURI);
final int sleepIntervalMilliseconds = 20;
final int maxWaitMilliseconds = KitodoConfig.getIntParameter(ParameterFileManagement.FILE_MAX_WAIT_MILLISECONDS);
boolean success;
int millisWaited = 0;
do {
if (SystemUtils.IS_OS_WINDOWS && millisWaited == sleepIntervalMilliseconds) {
logger.warn("Renaming {} failed. This is Windows. Running the garbage collector may yield good"
+ " results. Forcing immediate garbage collection now!",
fileToRename.getName());
System.gc();
}
success = fileToRename.renameTo(renamedFile);
if (!success) {
if (millisWaited == 0) {
logger.info("Renaming {} failed. File may be locked. Retrying...", fileToRename.getName());
}
waitForThread(sleepIntervalMilliseconds);
millisWaited += sleepIntervalMilliseconds;
}
} while (!success && millisWaited < maxWaitMilliseconds);
if (!success) {
logger.error("Rename {} failed. This is a permanent error. Giving up.", fileToRename.getName());
throw new IOException(
"Renaming of " + fileToRename.getName() + " into " + renamedFile.getName() + " failed.");
}
if (millisWaited > 0) {
logger.info("Rename finally succeeded after {} milliseconds.", Integer.toString(millisWaited));
}
return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(renamedFile.getPath()).toUri());
}
private void waitForThread(int sleepIntervalMilliseconds) {
try {
Thread.sleep(sleepIntervalMilliseconds);
} catch (InterruptedException e) {
logger.warn("The thread was interrupted");
Thread.currentThread().interrupt();
}
}
@Override
public boolean fileExist(URI uri) {
boolean exists = new File(fileMapper.mapUriToKitodoDataDirectoryUri(uri)).exists();
logger.trace(exists ? "Found {}" : "No such file: {}", uri);
return exists;
}
@Override
public boolean isFile(URI uri) {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
return new File(uri).isFile();
}
@Override
public boolean isDirectory(URI directory) {
directory = fileMapper.mapUriToKitodoDataDirectoryUri(directory);
return new File(directory).isDirectory();
}
@Override
public boolean canRead(URI uri) {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
return new File(uri).canRead();
}
@Override
public Integer getNumberOfFiles(FilenameFilter filter, URI directory) {
int count = 0;
directory = fileMapper.mapUriToKitodoDataDirectoryUri(directory);
if (filter == null) {
count += iterateOverDirectories(directory);
} else {
count += iterateOverSpecificDirectories(filter, directory);
}
return count;
}
/**
* Iterate over children directories of directory.
*
* @param directory
* as URI
* @return amount of files
*/
private Integer iterateOverDirectories(URI directory) {
int count = 0;
if (isDirectory(directory)) {
List<URI> children = getSubUris(null, directory);
for (URI child : children) {
child = fileMapper.mapUriToKitodoDataDirectoryUri(child);
if (isDirectory(child)) {
count += getNumberOfFiles(null, child);
} else {
count += 1;
}
}
}
return count;
}
/**
* Iterate over children specific directories of directory.
*
* @param directory
* as URI
* @return amount of specific (eg. image) files
*/
private Integer iterateOverSpecificDirectories(FilenameFilter filter, URI directory) {
int count = 0;
if (isDirectory(directory)) {
count = getSubUris(filter, directory).size();
List<URI> children = getSubUris(null, directory);
for (URI child : children) {
child = fileMapper.mapUriToKitodoDataDirectoryUri(child);
count += getNumberOfFiles(filter, child);
}
}
return count;
}
@Override
public Long getSizeOfDirectory(URI directory) throws IOException {
if (!directory.isAbsolute()) {
directory = fileMapper.mapUriToKitodoDataDirectoryUri(directory);
}
if (isDirectory(directory)) {
return FileUtils.sizeOfDirectory(new File(directory));
} else {
throw new IOException("Given URI doesn't point to the directory!");
}
}
@Override
public String getFileNameWithExtension(URI uri) {
return FilenameUtils.getName(uri.getPath());
}
@Override
public List<URI> getSubUris(FilenameFilter filter, URI uri) {
if (!uri.isAbsolute()) {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
}
List<URI> resultList = new ArrayList<>();
File[] files;
if (filter == null) {
files = listFiles(new File(uri));
} else {
files = listFiles(filter, new File(uri));
}
for (File file : files) {
URI tempURI = Paths.get(file.getPath()).toUri();
resultList.add(fileMapper.unmapUriFromKitodoDataDirectoryUri(tempURI));
}
return resultList;
}
/**
* Lists all Files at the given path.
*
* @param file
* the Directory to get the Files from
* @return an Array of Files
*/
private File[] listFiles(File file) {
File[] unchecked = file.listFiles();
return unchecked != null ? unchecked : new File[0];
}
/**
* Lists all files at the given path and with a given filter.
*
* @param file
* the directory to get the Files from
* @return an Array of Files
*/
private File[] listFiles(FilenameFilter filter, File file) {
File[] unchecked = file.listFiles(filter);
return unchecked != null ? unchecked : new File[0];
}
@Override
public URI createProcessLocation(String processId) throws IOException {
File processRootDirectory = new File(KitodoConfig.getKitodoDataDirectory() + File.separator + processId);
String scriptCreateDirMeta = KitodoConfig.getParameter("script_createDirMeta");
String command = scriptCreateDirMeta + ' ' + processRootDirectory.getPath();
if (!processRootDirectory.exists() && !commandService.runCommand(command.hashCode(), command).isSuccessful()) {
throw new IOException("Could not create processRoot directory.");
}
return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(processRootDirectory.getPath()).toUri());
}
@Override
public URI createUriForExistingProcess(String processId) {
return URI.create(processId);
}
@Override
public URI getProcessSubTypeUri(URI processBaseUri, String processTitle, ProcessSubType subType,
String resourceName) {
return URI.create(getProcessSubType(processBaseUri.toString(), processTitle, subType, resourceName));
}
/**
* Get part of URI specific for process and process sub type.
*
* @param processTitle
* tile of process
* @param processSubType
* object
* @param resourceName
* as String
* @return process specific part of URI
*/
private String getProcessSubType(String processID, String processTitle, ProcessSubType processSubType,
String resourceName) {
processTitle = encodeTitle(processTitle);
final String ocr = "/ocr/";
if (Objects.isNull(resourceName)) {
resourceName = "";
}
switch (processSubType) {
case IMAGE:
return processID + "/" + IMAGES_DIRECTORY_NAME + "/" + resourceName;
case IMAGE_SOURCE:
return getSourceDirectory(processID, processTitle) + resourceName;
case META_XML:
return processID + "/meta.xml";
case TEMPLATE:
return processID + "/template.xml";
case IMPORT:
return processID + "/import/" + resourceName;
case OCR:
return processID + ocr;
case OCR_PDF:
return processID + ocr + processTitle + "_pdf/" + resourceName;
case OCR_TXT:
return processID + ocr + processTitle + "_txt/" + resourceName;
case OCR_WORD:
return processID + ocr + processTitle + "_wc/" + resourceName;
case OCR_ALTO:
return processID + ocr + processTitle + "_alto/" + resourceName;
default:
return "";
}
}
/**
* Remove possible white spaces from process titles.
*
* @param title
* process title
* @return encoded process title
*/
private String encodeTitle(String title) {
if (title.contains(" ")) {
title = title.replace(" ", "__");
}
return title;
}
/**
* Gets the image source directory.
*
* @param processTitle
* title of the process, to get the source directory for
* @return the source directory as a string
*/
private URI getSourceDirectory(String processId, String processTitle) {
final String suffix = "_" + KitodoConfig.getParameter(ParameterFileManagement.DIRECTORY_SUFFIX, "tif");
URI dir = URI.create(getProcessSubType(processId, processTitle, ProcessSubType.IMAGE, null));
FilenameFilter filterDirectory = new FileNameEndsWithFilter(suffix);
URI sourceFolder = URI.create("");
try {
List<URI> directories = getSubUris(filterDirectory, dir);
if (directories.isEmpty()) {
sourceFolder = dir.resolve(processTitle + suffix + "/");
if (KitodoConfig.getBooleanParameter(ParameterFileManagement.CREATE_SOURCE_FOLDER, false)) {
if (!fileExist(dir)) {
createDirectory(dir.resolve(".."), IMAGES_DIRECTORY_NAME);
}
createDirectory(dir, processTitle + suffix);
}
} else {
sourceFolder = dir.resolve("/" + directories.get(0));
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return sourceFolder;
}
@Override
public boolean createSymLink(URI homeUri, URI targetUri, boolean onlyRead, String userLogin) {
File imagePath = new File(fileMapper.mapUriToKitodoDataDirectoryUri(homeUri));
File userHome = new File(getDecodedPath(targetUri));
if (userHome.exists()) {
return false;
}
String command = KitodoConfig.getParameter("script_createSymLink");
CommandService commandService = new CommandService();
List<String> parameters = new ArrayList<>();
parameters.add(imagePath.getAbsolutePath());
parameters.add(userHome.getAbsolutePath());
if (onlyRead) {
parameters.add(KitodoConfig.getParameter("UserForImageReading", "root"));
} else {
parameters.add(userLogin);
}
try {
return commandService.runCommand(new File(command), parameters).isSuccessful();
} catch (FileNotFoundException e) {
logger.error("FileNotFoundException in createSymLink", e);
return false;
} catch (IOException e) {
logger.error("IOException in createSymLink", e);
return false;
}
}
@Override
public boolean deleteSymLink(URI homeUri) {
File homeFile = new File(fileMapper.mapUriToKitodoDataDirectoryUri(homeUri));
String command = KitodoConfig.getParameter("script_deleteSymLink");
CommandService commandService = new CommandService();
List<String> parameters = new ArrayList<>();
try {
parameters.add(URLDecoder.decode(homeFile.getAbsolutePath(), StandardCharsets.UTF_8.name()));
return commandService.runCommand(new File(command), parameters).isSuccessful();
} catch (FileNotFoundException e) {
logger.error("FileNotFoundException in deleteSymLink", e);
return false;
} catch (IOException e) {
logger.error("IOException in deleteSymLink", e);
return false;
}
}
private String getDecodedPath(URI uri) {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
String uriToDecode = new File(uri).getPath();
String decodedPath;
try {
decodedPath = URLDecoder.decode(uriToDecode, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
return "";
}
return decodedPath;
}
public File getFile(URI uri) {
uri = fileMapper.mapUriToKitodoDataDirectoryUri(uri);
return new File(uri);
}
}