diff --git a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java index bff46b96a6e..a1f24896a36 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java @@ -16,11 +16,9 @@ public interface CommandInterface { /** * Runs a given command. * - * @param id - * The id, to identify the command and it's results. * @param command * The command as a String. * @return A commandResult, which contains id and result messages. */ - CommandResult runCommand(Integer id, String command); + CommandResult runCommand(String command); } diff --git a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java index 7cc294b3f9e..7b89ed36458 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java @@ -16,9 +16,6 @@ public class CommandResult { - /** The id of the command. */ - private Integer id; - /** The command as a String. */ private String command; @@ -31,8 +28,6 @@ public class CommandResult { /** * Constructor. * - * @param id - * The id. * @param command * The command. * @param successful @@ -40,22 +35,12 @@ public class CommandResult { * @param messages * The resultMessages */ - public CommandResult(Integer id, String command, boolean successful, List messages) { - this.id = id; + public CommandResult(String command, boolean successful, List messages) { this.command = command; this.successful = successful; this.messages = messages; } - /** - * Gets the id. - * - * @return The id. - */ - public Integer getId() { - return id; - } - /** * Gets the command. * @@ -99,8 +84,7 @@ public boolean equals(Object object) { if (object instanceof CommandResult) { CommandResult that = (CommandResult) object; - return this.id.equals(that.id) - && this.successful == that.successful + return this.successful == that.successful && this.command.equals(that.command) && this.messages.equals(that.messages); } @@ -109,6 +93,6 @@ public boolean equals(Object object) { @Override public int hashCode() { - return Objects.hash(id, successful, command, messages); + return Objects.hash(successful, command, messages); } } diff --git a/Kitodo-Command/src/main/java/org/kitodo/command/Command.java b/Kitodo-Command/src/main/java/org/kitodo/command/Command.java index 5ec8ec1ad2b..835ddaeee25 100644 --- a/Kitodo-Command/src/main/java/org/kitodo/command/Command.java +++ b/Kitodo-Command/src/main/java/org/kitodo/command/Command.java @@ -31,14 +31,12 @@ public class Command implements CommandInterface { /** * Method executes a script. * - * @param id - * The id, to identify the command and it's results. * @param command * The command as a String. * @return The command result. */ @Override - public CommandResult runCommand(Integer id, String command) { + public CommandResult runCommand(String command) { CommandResult commandResult; Process process; String[] callSequence = command.split("[\\r\\n\\s]+"); @@ -53,17 +51,17 @@ public CommandResult runCommand(Integer id, String command) { outputMessage.addAll(errorMessage); - commandResult = new CommandResult(id, command, errCode == 0, outputMessage); + commandResult = new CommandResult(command, errCode == 0, outputMessage); if (commandResult.isSuccessful()) { - logger.info("Execution of Command {} {} was successful!: {}", commandResult.getId(), + logger.info("Execution of Command {} was successful!: {}", commandResult.getCommand(), commandResult.getMessages()); } else { - logger.error("Execution of Command {} {} failed!: {}", commandResult.getId(), + logger.error("Execution of Command {} failed!: {}", commandResult.getCommand(), commandResult.getMessages()); } } } catch (InterruptedException e) { - commandResult = new CommandResult(id, command, false, Collections.singletonList(e.getMessage())); + commandResult = new CommandResult(command, false, Collections.singletonList(e.getMessage())); logger.error("Execution of Command Thread was interrupted!"); Thread.currentThread().interrupt(); return commandResult; @@ -71,8 +69,8 @@ public CommandResult runCommand(Integer id, String command) { List errorMessages = new ArrayList<>(); errorMessages.add(e.getCause().toString()); errorMessages.add(e.getMessage()); - commandResult = new CommandResult(id, command, false, errorMessages); - logger.error("Execution of Command {} {} failed!: {}", commandResult.getId(), commandResult.getCommand(), + commandResult = new CommandResult(command, false, errorMessages); + logger.error("Execution of Command {} failed!: {}", commandResult.getCommand(), commandResult.getMessages()); return commandResult; } diff --git a/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java b/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java index b32a98ccd29..a09e467dbe3 100644 --- a/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java +++ b/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java @@ -31,7 +31,6 @@ public class CommandTest { private static String scriptExtension; - private int processId = 3; private static boolean windows = false; private static File workingScript = new File( System.getProperty("user.dir") + "/src/test/resources/working_script.sh"); @@ -70,7 +69,7 @@ public void shouldRunCommand() { Command command = new Command(); String commandString = "src/test/resources/working_script" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); List expectedMessages = new ArrayList<>(); if (windows) { @@ -80,7 +79,7 @@ public void shouldRunCommand() { expectedMessages.add("Hello World"); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, true, expectedMessages); + CommandResult expectedCommandResult = new CommandResult(commandString, true, expectedMessages); assertEquals("successful booleans of CommandResults are not identical", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -95,9 +94,9 @@ public void shouldNotRunNotExistingCommand() { Command command = new Command(); String commandString = "src/test/resources/notExistingScript" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, false, null); + CommandResult expectedCommandResult = new CommandResult(commandString, false, null); assertEquals("Should not run not existing Command", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -108,9 +107,9 @@ public void shouldNotRunCommandWithFalseSyntax() { Command command = new Command(); String commandString = "src/test/resources/not_working_script" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, false, null); + CommandResult expectedCommandResult = new CommandResult(commandString, false, null); assertEquals("Should not run command with false syntax", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -121,7 +120,7 @@ public void shouldRunCommandWithParameter() { Command command = new Command(); String commandString = "src/test/resources/working_script_with_parameters" + scriptExtension + " testParameter"; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); ArrayList expectedMessages = new ArrayList<>(); @@ -132,7 +131,7 @@ public void shouldRunCommandWithParameter() { expectedMessages.add("testParameter"); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, true, expectedMessages); + CommandResult expectedCommandResult = new CommandResult(commandString, true, expectedMessages); assertEquals("successful booleans of CommandResults are not identical", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java index 969004ef01b..dc69926aca4 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java @@ -116,6 +116,9 @@ public class Process extends BaseTemplateBean { @Column(name = "inChoiceListShown") Boolean inChoiceListShown; + @Column(name = "ocrd_workflow_id") + private String ocrdWorkflowId; + @Transient private User blockedUser; @@ -651,4 +654,23 @@ public void setNumberOfImages(int numberOfImages) { public void setNumberOfStructures(int numberOfStructures) { this.numberOfStructures = numberOfStructures; } + + /** + * Get OCR-D workflow identifier. + * + * @return The OCR-D workflow identifier + */ + public String getOcrdWorkflowId() { + return ocrdWorkflowId; + } + + /** + * Set the OCR-D workflow identifier. + * + * @param ocrdWorkflowId + * The identifier of the OCR-D workflow + */ + public void setOcrdWorkflowId(String ocrdWorkflowId) { + this.ocrdWorkflowId = ocrdWorkflowId; + } } diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java index 93e997ab8ce..e26c256a697 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java @@ -65,6 +65,9 @@ public class Template extends BaseTemplateBean { @JoinColumn(name = "project_id", foreignKey = @ForeignKey(name = "FK_project_x_template_project_id")) }) private List projects; + @Column(name = "ocrd_workflow_id") + private String ocrdWorkflowId; + /** * Constructor. */ @@ -168,6 +171,26 @@ public void setWorkflow(Workflow workflow) { this.workflow = workflow; } + + /** + * Get OCR-D workflow identifier. + * + * @return value of OCR-D workflow identifier + */ + public String getOcrdWorkflowId() { + return ocrdWorkflowId; + } + + /** + * Set the OCR-D workflow identifier. + * + * @param ocrdWorkflowId + * The identifier of the OCR-D workflow + */ + public void setOcrdWorkflowId(String ocrdWorkflowId) { + this.ocrdWorkflowId = ocrdWorkflowId; + } + /** * Get projects list. * diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql new file mode 100644 index 00000000000..336275943f2 --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql @@ -0,0 +1,17 @@ +-- +-- (c) Kitodo. Key to digital objects e. V. +-- +-- 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. +-- + +-- Add authority to assign OCR-D workflow in template or process details +INSERT IGNORE INTO authority (title) VALUES ('assignOcrdWorkflow_clientAssignable'); + +-- Add columns of OCR-D workflow identifier +ALTER TABLE process ADD ocrd_workflow_id varchar(255) DEFAULT NULL; +ALTER TABLE template ADD ocrd_workflow_id varchar(255) DEFAULT NULL; diff --git a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java index bd015ce934d..ce7c1901517 100644 --- a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java +++ b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java @@ -14,7 +14,6 @@ import java.io.File; import java.io.IOException; import java.util.List; -import java.util.Random; import org.kitodo.api.command.CommandInterface; import org.kitodo.api.command.CommandResult; @@ -22,8 +21,6 @@ class CommandService { - private Random random = new Random(1000000); - /** * Method executes a script string. * @@ -39,7 +36,7 @@ CommandResult runCommand(String script) throws IOException { KitodoServiceLoader serviceLoader = new KitodoServiceLoader<>(CommandInterface.class); CommandInterface command = serviceLoader.loadModule(); - CommandResult commandResult = command.runCommand(random.nextInt(), script); + CommandResult commandResult = command.runCommand(script); List commandResultMessages = commandResult.getMessages(); if (commandResultMessages.size() > 0 && commandResultMessages.get(0).contains("IOException")) { throw new IOException(commandResultMessages.get(1)); diff --git a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java index 0a2c8702e2f..2540560fcbb 100644 --- a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java +++ b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java @@ -393,7 +393,7 @@ 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()) { + if (!processRootDirectory.exists() && !commandService.runCommand(command).isSuccessful()) { throw new IOException("Could not create processRoot directory."); } return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(processRootDirectory.getPath()).toUri()); diff --git a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java index 755ac4b7bf7..70fee5f5879 100644 --- a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java +++ b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java @@ -46,6 +46,12 @@ public enum ParameterCore implements ParameterInterface { */ DIR_RULESETS(new Parameter("directory.rulesets")), + /** + * Absolute path to the directory that the OCR-D workflow files will be + * read from. It must be terminated by a directory separator ("/"). + */ + OCRD_WORKFLOWS_DIR(new Parameter<>("ocrd.workflows.directory", "")), + /** * Absolute path to the directory that XSLT files are stored in which are used * to transform the "XML log" (as visible from the XML button in the processes @@ -639,8 +645,12 @@ public enum ParameterCore implements ParameterInterface { * Secret is used to encrypt or decrypt LDAP manager passwords which are stored in the database in encrypted form. * Once the secret value is set, it should not be changed since encrypted data can no longer be decrypted. */ - SECURITY_SECRET_LDAPMANAGERPASSWORD(new Parameter<>("security.secret.ldapManagerPassword", "")); + SECURITY_SECRET_LDAPMANAGERPASSWORD(new Parameter<>("security.secret.ldapManagerPassword", "")), + /* Optional parameter can be used to limit the number of processes for which media renaming can be conducted as a + * list function. Values different from positive integers are interpreted as "unlimited". + */ + MAX_NUMBER_OF_PROCESSES_FOR_MEDIA_RENAMING(new Parameter<>("maxNumberOfProcessesForMediaRenaming", -1)); private final Parameter parameter; diff --git a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java index 032f46ef4ba..72eb057e8e4 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java @@ -222,6 +222,15 @@ public boolean hasAuthorityToAddOnProjectPage() { return securityAccessService.hasAuthorityToAddOnProjectPage(); } + /** + * Check if the current user has the authority to add OCR-D workflow. + * + * @return true if the current user has the authority to add OCR-D workflow. + */ + public boolean hasAuthorityToAssignOcrdWorkflow() { + return securityAccessService.hasAuthorityToAssignOcrdWorkflow(); + } + /** * Check if the current user has the authority to delete the batch. * diff --git a/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java b/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java new file mode 100644 index 00000000000..4070df06cf5 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java @@ -0,0 +1,45 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.production.converter; + +import java.util.Objects; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; +import javax.inject.Named; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.kitodo.production.services.ServiceManager; + + +@Named +public class OcrdWorkflowConverter extends BeanConverter implements Converter { + + @Override + public Object getAsObject(FacesContext context, UIComponent component, String value) { + if (StringUtils.isNotEmpty(value)) { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(value); + } + return null; + } + + @Override + public String getAsString(FacesContext context, UIComponent component, Object value) { + if (Objects.nonNull(value) && value instanceof Pair) { + return (String) ((Pair) value).getKey(); + } + return null; + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java index 18cb425973e..0bd56c0dcbf 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java @@ -30,6 +30,8 @@ import javax.inject.Inject; import javax.inject.Named; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.config.ConfigCore; @@ -61,6 +63,7 @@ import org.kitodo.production.services.data.ProcessService; import org.kitodo.production.services.file.FileService; import org.kitodo.production.services.workflow.WorkflowControllerService; +import org.omnifaces.util.Ajax; import org.primefaces.PrimeFaces; import org.primefaces.event.SelectEvent; import org.primefaces.event.ToggleSelectEvent; @@ -89,6 +92,7 @@ public class ProcessForm extends TemplateBaseForm { private String processEditReferer = DEFAULT_LINK; private String taskEditReferer = DEFAULT_LINK; + private String errorMessage = ""; private List customColumns; @@ -874,6 +878,47 @@ public List getProjects() { return ServiceManager.getProjectService().getAllForSelectedClient(); } + /** + * Get list of OCR-D workflows for select list. + * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflows(); + } + + /** + * Get the OCR-D workflow. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflow() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(process.getOcrdWorkflowId()); + } + + /** + * Get the OCR-D workflow of process template. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflowOfTemplate() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(process.getTemplate().getOcrdWorkflowId()); + } + + /** + * Set the OCR-D workflow. + * + * @param ocrdWorkflow + * The immutable key value pair + */ + public void setOcrdWorkflow(Pair ocrdWorkflow) { + String ocrdWorkflowId = StringUtils.EMPTY; + if (Objects.nonNull(ocrdWorkflow)) { + ocrdWorkflowId = ocrdWorkflow.getKey().toString(); + } + process.setOcrdWorkflowId(ocrdWorkflowId); + } + /** * Get rulesets for select list. * @@ -1182,4 +1227,46 @@ private int getProcessId(Object process) { public FilterMenu getFilterMenu() { return filterMenu; } + + /** + * Rename media files of all selected processes. + */ + public void renameMedia() { + List processes = getSelectedProcesses(); + errorMessage = ServiceManager.getFileService().tooManyProcessesSelectedForMediaRenaming(processes.size()); + if (StringUtils.isBlank(errorMessage)) { + PrimeFaces.current().executeScript("PF('renameMediaConfirmDialog').show();"); + } else { + Ajax.update("errorDialog"); + PrimeFaces.current().executeScript("PF('errorDialog').show();"); + } + } + + /** + * Start renaming media files of selected processes. + */ + public void startRenaming() { + ServiceManager.getFileService().renameMedia(getSelectedProcesses()); + PrimeFaces.current().executeScript("PF('notifications').renderMessage({'summary':'" + + Helper.getTranslation("renamingMediaFilesOfSelectedProcessesStarted") + + "','severity':'info'})"); + } + + /** + * Return media renaming confirmation message with number of processes affected. + * + * @return media renaming confirmation message + */ + public String getMediaRenamingConfirmMessage() { + return Helper.getTranslation("renameMediaForProcessesConfirmMessage", + String.valueOf(getSelectedProcesses().size())); + } + + /** + * Get error message. + * @return error message + */ + public String getErrorMessage() { + return errorMessage; + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java index 2af30725d4d..e852d31f89b 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java @@ -24,6 +24,7 @@ import javax.inject.Named; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.data.database.beans.Docket; @@ -274,6 +275,38 @@ public List getWorkflows() { return ServiceManager.getWorkflowService().getAvailableWorkflows(); } + /** + * Get list of OCR-D workflows for select list. + * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflows(); + } + + /** + * Get the OCR-D workflow. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflow() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(template.getOcrdWorkflowId()); + } + + /** + * Set the OCR-D workflow. + * + * @param ocrdWorkflow + * The immutable key value pair + */ + public void setOcrdWorkflow(Pair ocrdWorkflow) { + String ocrdWorkflowId = StringUtils.EMPTY; + if (Objects.nonNull(ocrdWorkflow)) { + ocrdWorkflowId = ocrdWorkflow.getKey().toString(); + } + template.setOcrdWorkflowId(ocrdWorkflowId); + } + /** * Check if user is not assigned to the project. Used for disabling projects. * diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java b/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java index 43571f8e4f8..2ff1e071157 100644 --- a/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java +++ b/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.api.dataformat.LogicalDivision; @@ -70,10 +71,9 @@ private enum MetadataLevel { * be replaced. */ private static final Pattern VARIABLE_FINDER_REGEX = Pattern.compile( - "(\\$?)\\((?:(prefs|processid|processtitle|projectid|stepid|stepname|generatorsource|generatorsourcepath)|" - + "(?:(meta|process|product|template)\\.(?:(firstchild|topstruct)\\.)?([^)]+)|" - + "(?:(filename|basename|relativepath))))\\)"); - + "(\\$?)\\((?:(prefs|processid|processtitle|projectid|stepid|stepname|generatorsource|generatorsourcepath|ocrdworkflowid)|" + + "(?:(meta|process|product|template)\\.(?:(firstchild|topstruct)\\.)?([^)]+)|" + + "(?:(filename|basename|relativepath))))\\)"); /** * The map is filled with replacement instructions that are required for * backwards compatibility with version 2. @@ -244,6 +244,8 @@ private String determineReplacementForInternalValue(Matcher variableFinder) { case "generatorsource" : case "generatorsourcepath": return determineReplacementForGeneratorSource(variableFinder, variableFinder.group(2)); + case "ocrdworkflowid": + return determineReplacementForOcrdWorkflowId(variableFinder); default: logger.warn("Cannot replace \"{}\": no such case defined in switch", variableFinder.group()); return variableFinder.group(); @@ -281,6 +283,28 @@ private String determineReplacementForProcessid(Matcher variableFinder) { return variableFinder.group(1) + process.getId().toString(); } + private String determineReplacementForOcrdWorkflowId(Matcher variableFinder) { + if (Objects.isNull(process)) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": no process given"); + return variableFinder.group(1); + } + + if (StringUtils.isNotEmpty(process.getOcrdWorkflowId())) { + return variableFinder.group(1) + process.getOcrdWorkflowId(); + } + + if (Objects.isNull(process.getTemplate())) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": process has no template assigned"); + return variableFinder.group(1); + } + + if (StringUtils.isEmpty(process.getTemplate().getOcrdWorkflowId())) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": template has no OCR-D workflow assigned"); + return variableFinder.group(1); + } + return variableFinder.group(1) + process.getTemplate().getOcrdWorkflowId(); + } + private String determineReplacementForProcesstitle(Matcher variableFinder) { if (Objects.isNull(process)) { logger.warn("Cannot replace \"(processtitle)\": no process given"); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java b/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java index 10bbe24abe1..30c82187d79 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java @@ -48,6 +48,7 @@ import org.kitodo.production.services.image.ImageService; import org.kitodo.production.services.index.IndexingService; import org.kitodo.production.services.migration.MigrationService; +import org.kitodo.production.services.ocr.OcrdWorkflowService; import org.kitodo.production.services.schema.SchemaService; import org.kitodo.production.services.security.SecurityAccessService; import org.kitodo.production.services.security.SessionService; @@ -83,6 +84,7 @@ public class ServiceManager { private static MetsService metsService; private static MigrationService migrationService; private static ImportConfigurationService importConfigurationService; + private static OcrdWorkflowService ocrdWorkflowService; private static PropertyService propertyService; private static ProcessService processService; private static ProjectService projectService; @@ -201,6 +203,12 @@ private static void initializeFolderService() { } } + private static void initializeOcrdWorkflowService() { + if (Objects.isNull(ocrdWorkflowService)) { + ocrdWorkflowService = OcrdWorkflowService.getInstance(); + } + } + private static void initializeProjectService() { if (Objects.isNull(projectService)) { projectService = ProjectService.getInstance(); @@ -507,6 +515,17 @@ public static FolderService getFolderService() { return folderService; } + /** + * Initialize OcrdWorkflowService if it is not yet initialized and next return + * it. + * + * @return OcrdWorkflowService object + */ + public static OcrdWorkflowService getOcrdWorkflowService() { + initializeOcrdWorkflowService(); + return ocrdWorkflowService; + } + /** * Initialize ProjectService if it is not yet initialized and next return * it. diff --git a/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java b/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java index 4ce4a48a962..9d67eaf1571 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java @@ -19,17 +19,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Random; import org.kitodo.api.command.CommandInterface; import org.kitodo.api.command.CommandResult; -import org.kitodo.production.services.ServiceManager; import org.kitodo.serviceloader.KitodoServiceLoader; public class CommandService { private final CommandInterface commandModule; private final ArrayList finishedCommandResults = new ArrayList<>(); - private final Random random = new Random(1000000); /** * Initialize Command Service. @@ -54,7 +51,7 @@ public CommandResult runCommand(String script) throws IOException { if (Objects.isNull(script)) { return null; } - CommandResult commandResult = commandModule.runCommand(random.nextInt(), script); + CommandResult commandResult = commandModule.runCommand(script); List commandResultMessages = commandResult.getMessages(); if (!commandResultMessages.isEmpty() && commandResultMessages.get(0).contains("IOException")) { throw new IOException(commandResultMessages.get(1)); @@ -110,7 +107,7 @@ public CommandResult runCommand(File scriptFile) throws IOException { public void runCommandAsync(String script) { if (Objects.nonNull(script)) { Flowable source = Flowable.fromCallable(() -> - commandModule.runCommand(random.nextInt(), script) + commandModule.runCommand(script) ); Flowable commandBackgroundWorker = source.subscribeOn(Schedulers.io()); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java b/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java index 476a5dde7ef..a5fb16915b0 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -33,6 +34,7 @@ import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; @@ -64,11 +66,14 @@ import org.kitodo.production.helper.metadata.ImageHelper; import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper; import org.kitodo.production.helper.metadata.pagination.Paginator; +import org.kitodo.production.helper.tasks.TaskManager; import org.kitodo.production.metadata.MetadataEditor; +import org.kitodo.production.metadata.MetadataLock; import org.kitodo.production.model.Subfolder; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.command.CommandService; import org.kitodo.production.services.data.RulesetService; +import org.kitodo.production.thread.RenameMediaThread; import org.kitodo.serviceloader.KitodoServiceLoader; import org.kitodo.utils.MediaUtil; @@ -1524,6 +1529,52 @@ public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMa return numberOfRenamedMedia; } + /** + * Check whether too many processes are selected for media renaming and return corresponding error message. + * @param numberOfProcesses number of processes for media renaming + * @return error message if too many processes are selected; otherwise return empty string + */ + public String tooManyProcessesSelectedForMediaRenaming(int numberOfProcesses) { + int limit = ConfigCore.getIntParameterOrDefaultValue(ParameterCore.MAX_NUMBER_OF_PROCESSES_FOR_MEDIA_RENAMING); + if (0 < limit && limit < numberOfProcesses) { + return Helper.getTranslation("tooManyProcessesSelectedForMediaRenaming", String.valueOf(limit), + String.valueOf(numberOfProcesses)); + } else { + return ""; + } + } + + /** + * Rename media files of given processes. + * @param processes Processes whose media is renamed + */ + public void renameMedia(List processes) { + processes = lockAndSortProcessesForRenaming(processes); + TaskManager.addTask(new RenameMediaThread(processes)); + } + + private List lockAndSortProcessesForRenaming(List processes) { + processes.sort(Comparator.comparing(Process::getId)); + List lockedProcesses = new LinkedList<>(); + for (Process process : processes) { + int processId = process.getId(); + if (MetadataLock.isLocked(processId)) { + lockedProcesses.add(processId); + if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.ANONYMIZE)) { + logger.error("Unable to lock process " + processId + " for media renaming because it is currently " + + "being worked on by another user"); + } else { + User currentUser = MetadataLock.getLockUser(processId); + logger.error("Unable to lock process " + processId + " for media renaming because it is currently " + + "being worked on by another user (" + currentUser.getFullName() + ")"); + } + } else { + MetadataLock.setLocked(processId, ServiceManager.getUserService().getCurrentUser()); + } + } + return processes.stream().filter(p -> !lockedProcesses.contains(p.getId())).collect(Collectors.toList()); + } + /** * Revert renaming of media files when the user leaves the metadata editor without saving. This method uses a * provided map object to rename media files identified by the map entries values to the corresponding map entries @@ -1534,6 +1585,7 @@ public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMa */ public void revertRenaming(BidiMap filenameMappings, Workpiece workpiece) { // revert media variant URIs for all media files in workpiece to previous, original values + logger.info("Reverting to original media filenames of process " + workpiece.getId()); for (PhysicalDivision physicalDivision : workpiece .getAllPhysicalDivisionChildrenFilteredByTypes(PhysicalDivision.TYPES)) { for (Entry mediaVariantURIEntry : physicalDivision.getMediaFiles().entrySet()) { @@ -1545,8 +1597,14 @@ public void revertRenaming(BidiMap filenameMappings, Workpiece workpie try { List tempUris = new LinkedList<>(); for (Entry mapping : filenameMappings.entrySet()) { - tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString() - + TEMP_EXTENSION)); + if (mapping.getKey().toString().endsWith(TEMP_EXTENSION)) { + // if current URI has '.tmp' extension, directly revert to original name (without '.tmp' extension) + tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString())); + } else { + // rename to new filename with '.tmp' extension otherwise + tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString() + + TEMP_EXTENSION)); + } } for (URI tempUri : tempUris) { fileManagementModule.rename(tempUri, StringUtils.removeEnd(tempUri.toString(), TEMP_EXTENSION)); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java b/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java new file mode 100644 index 00000000000..ab8d4f41e18 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java @@ -0,0 +1,102 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.production.services.ocr; + +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.config.ConfigCore; +import org.kitodo.config.enums.ParameterCore; + +public class OcrdWorkflowService { + + private static final Logger logger = LogManager.getLogger(OcrdWorkflowService.class); + private static volatile OcrdWorkflowService instance = null; + + /** + * Return singleton variable of type OcrdWorkflowService. + * + * @return unique instance of OcrdWorkflowService + */ + public static OcrdWorkflowService getInstance() { + OcrdWorkflowService localReference = instance; + if (Objects.isNull(localReference)) { + synchronized (OcrdWorkflowService.class) { + localReference = instance; + if (Objects.isNull(localReference)) { + localReference = new OcrdWorkflowService(); + instance = localReference; + } + } + } + return localReference; + } + + /** + * Get list of OCR-D workflows from directory. + *

+ * The files are relative paths to the OCR-D workflow directory. + *

+ * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + String ocrdWorkflowsDirectoryConfig = ConfigCore.getParameterOrDefaultValue(ParameterCore.OCRD_WORKFLOWS_DIR); + if (StringUtils.isNotEmpty(ocrdWorkflowsDirectoryConfig)) { + Path ocrdWorkflowsDirectory = Path.of(ocrdWorkflowsDirectoryConfig); + if (Files.isDirectory(ocrdWorkflowsDirectory)) { + try (Stream ocrProfilePaths = Files.walk(ocrdWorkflowsDirectory, FileVisitOption.FOLLOW_LINKS)) { + return ocrProfilePaths.filter(Files::isRegularFile).map(Path::toAbsolutePath).sorted() + .map(path -> getImmutablePairFromPath(path, ocrdWorkflowsDirectory)) + .collect(Collectors.toList()); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + } + return new ArrayList<>(); + } + + private static Pair getImmutablePairFromPath(Path filePath, Path ocrdWorkflowsDirectory) { + String path = filePath.toString(); + path = path.replace(ocrdWorkflowsDirectory.toString(), StringUtils.EMPTY); + path = StringUtils.removeStart(path, "/"); + return ImmutablePair.of(path, path); + } + + /** + * Get an OCR-D workflow by identifier. + * + * @param ocrdWorkflowId The OCR-D workflow identifier + * @return The OCR-D workflow + */ + public Pair getOcrdWorkflow(String ocrdWorkflowId) { + if (StringUtils.isNotEmpty(ocrdWorkflowId)) { + return getOcrdWorkflows().stream().filter(pair -> pair.getKey().equals(ocrdWorkflowId)).findFirst() + .orElse(null); + } + return null; + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java index 963344c9b9d..97a1967786a 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java @@ -233,6 +233,15 @@ public boolean hasAuthorityToAddOnProjectPage() { return hasAnyAuthorityForClient("addProject, addTemplate, addWorkflow, addDocket, addRuleset"); } + /** + * Check if the current user has the authority to assign an OCR-D workflow to template or process. + * + * @return true if the current user has the authority to assign an OCR-D workflow + */ + public boolean hasAuthorityToAssignOcrdWorkflow() { + return hasAnyAuthorityForClient("assignOcrdWorkflow"); + } + /** * Check if the current user has the authority to delete the batch. * diff --git a/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java b/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java new file mode 100644 index 00000000000..305e1684ade --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java @@ -0,0 +1,89 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * 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.production.thread; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.api.dataformat.Workpiece; +import org.kitodo.data.database.beans.Process; +import org.kitodo.production.helper.Helper; +import org.kitodo.production.helper.LocaleHelper; +import org.kitodo.production.helper.tasks.EmptyTask; +import org.kitodo.production.metadata.MetadataLock; +import org.kitodo.production.services.ServiceManager; + +/** + * This class is used to rename media files of multiple processes in a separate thread whose progress can be monitored + * in the task manager. + */ +public class RenameMediaThread extends EmptyTask { + + private static final Logger logger = LogManager.getLogger(RenameMediaThread.class); + private final List processes; + private static final String THREAD_NAME = "renameMediaThread"; + private static final String ERRORS_OCCURRED_KEY = "errorsOccurredCheckLog"; + private static final String PROCESSES = "processes"; + + public RenameMediaThread(List processes) { + super(processes.size() + " " + Helper.getString(LocaleHelper.getCurrentLocale(), PROCESSES)); + this.processes = processes; + } + + @Override + protected void setNameDetail(String detail) { + String localThreadName = Helper.getString(LocaleHelper.getCurrentLocale(), THREAD_NAME); + super.setName(localThreadName + ": " + detail); + } + + /** + * Run method of this thread. Iterates over processes and renames all media files in each process using the regular + * "media renaming" functionality of the FileService. + */ + @Override + public void run() { + for (Process process : processes) { + int processId = process.getId(); + URI metaXmlUri = ServiceManager.getProcessService().getMetadataFileUri(process); + DualHashBidiMap renamingMap = new DualHashBidiMap<>(); + Workpiece workpiece = null; + try { + workpiece = ServiceManager.getMetsService().loadWorkpiece(metaXmlUri); + int numberOfRenamedFiles = ServiceManager.getFileService().renameMediaFiles(process, workpiece, + renamingMap); + try (OutputStream out = ServiceManager.getFileService().write(metaXmlUri)) { + ServiceManager.getMetsService().save(workpiece, out); + logger.info("Renamed " + numberOfRenamedFiles + " media files for process " + process.getId()); + } + } catch (IOException | URISyntaxException e) { + logger.error(e.getMessage()); + String nameDetailMessage = processes.size() + + " " + Helper.getString(LocaleHelper.getCurrentLocale(), PROCESSES) + + " (" + Helper.getString(LocaleHelper.getCurrentLocale(), ERRORS_OCCURRED_KEY) + ")"; + this.setNameDetail(nameDetailMessage); + if (Objects.nonNull(workpiece)) { + ServiceManager.getFileService().revertRenaming(renamingMap.inverseBidiMap(), workpiece); + } + } + MetadataLock.setFree(processId); + setProgress((100 / processes.size()) * (processes.indexOf(process) + 1)); + } + setProgress(100); + } +} diff --git a/Kitodo/src/main/resources/kitodo_config.properties b/Kitodo/src/main/resources/kitodo_config.properties index 4f00df84098..e1fb72ff1c8 100644 --- a/Kitodo/src/main/resources/kitodo_config.properties +++ b/Kitodo/src/main/resources/kitodo_config.properties @@ -71,6 +71,9 @@ directory.modules=/usr/local/kitodo/modules/ # Falls Dateien zum Debuggen / Tracen geschrieben werden sollen, hier ein Verzeichnis angeben directory.debug=/usr/local/kitodo/debug/ +# Absolute path to the directory that the OCR-D workflow files will be +# read from. It must be terminated by a directory separator ("/"). +ocrd.workflows.directory=/usr/local/kitodo/ocrd_workflows/ # ----------------------------------- # Directory management @@ -764,3 +767,7 @@ security.secret.ldapManagerPassword= # largest data tables of tasks by processingBegin and process by creation date are considered. # The dates can be defined & separated in the format YYYY, YYYY-MM or YYYY-MM-DD e.g. 2017-05-10,2018-06,2022 # database.subset.dates= + +# This optional parameter can be used to limit the number of processes for which media renaming can be conducted as a +# list function. Values different from positive integers are interpreted as "unlimited". +#maxNumberOfProcessesForMediaRenaming=10000 diff --git a/Kitodo/src/main/resources/messages/errors_de.properties b/Kitodo/src/main/resources/messages/errors_de.properties index ac762c67701..518376503f2 100644 --- a/Kitodo/src/main/resources/messages/errors_de.properties +++ b/Kitodo/src/main/resources/messages/errors_de.properties @@ -133,6 +133,7 @@ noUserInStep=Der Aufgabe {0} wurden keine Nutzer zugeordnet. # O errorOccurred=Es ist ein Fehler aufgetreten: +errorsOccurredCheckLog=Es sind Fehler aufgetreten. Bitte \u00FCberprf\u00FCfen Sie kitodo.log f\u00FCr zus\u00E4tzliche Details. # P errorParsingFile=Parsingfehler: verschiedene {0} in der Datei (''{1}'' & ''{2}''). @@ -174,6 +175,7 @@ templateAssignedError=Der Workflow kann nicht gel\u00F6scht werden, da diesem Pr templateTitleAlreadyInUse=Der Produktionsvorlagentitel wird bereits verwendet. templateTitleEmpty=Kein Produktionsvorlagentitel angegeben. tooManyBatchesSelected=Es sind mehrere Batches ausgew\u00E4hlt. W\u00E4hlen Sie genau einen Batch aus, um diese Funktion zu nutzen. +tooManyProcessesSelectedForMediaRenaming=Es wurden {0} Vorg\u00E4nge f\u00FCr die Medienumbenennung ausgew\u00E4lt. Die maximal erlaubte Anzahl von Vorg\u00E4ngen f\u00FCr die Medienumbenennung liegt bei {1}. Bitte reduzieren Sie die Menge der ausgew\u00E4lten Vorg\u00E4nge und wiederholen Sie die Aktion. # U errorUploading=Fehler beim Hochladen von ''{0}''. diff --git a/Kitodo/src/main/resources/messages/errors_en.properties b/Kitodo/src/main/resources/messages/errors_en.properties index 656723aab9d..4dabbc2e150 100644 --- a/Kitodo/src/main/resources/messages/errors_en.properties +++ b/Kitodo/src/main/resources/messages/errors_en.properties @@ -133,6 +133,7 @@ noUserInStep=No user assigned to step {0}. # O errorOccurred=An error has occurred: +errorsOccurredCheckLog=Errors occurred. Please check kitodo.log for Details. # P errorParsingFile=Error parsing: various {0} in the file (''{1}'' & ''{2}''). @@ -173,6 +174,7 @@ templateAssignedError=The workflow could not be deleted because there are alread templateTitleAlreadyInUse=The template title is already in use. templateTitleEmpty=No template title stated. tooManyBatchesSelected=Multiple batches are selected. To use this function, please select one batch only. +tooManyProcessesSelectedForMediaRenaming={0} have been selected for media renaming. The maximum number of processes for media renaming is {1}. Please reduce the number of selected processes and try again. # U errorUploading=Error uploading ''{0}''. diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index e2afac176db..76b09c08330 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -59,6 +59,7 @@ ARCHIVED=Archiviert assigned=Zugewiesen assignedTo=In Bearbeitung durch assignMedia=und ausgew\u00E4hlte Medien zuweisen +assignOcrdWorkflow=OCR-D Workflow zuweisen assignToNextElement=Mit n\u00E4chstem Element verkn\u00FCpfen ats=ATS actions=Aktionen @@ -327,7 +328,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Strukturelemente k\u00F6nnen dataEditor.selectMetadataTask=Aufgabe w\u00E4hlen dataEditor.layoutSavedSuccessfullyTitle=Aktuelle Spaltenaufteilung erfolgreich gespeichert dataEditor.layoutSavedSuccessfullyText=Ihre aktuellen Metadaten-Editor-Einstellungen wurden erfolgreich gespeichert! Sie werden f\u00FCr alle zuk\u00FCnftigen Aufgaben dieses Typs wiederverwendet. -dataEditor.renameMedia=Medien umbenennen dataEditor.renamingMediaComplete=Das Umbenennen der Medien ist abgeschlossen dataEditor.renamingMediaError=Beim Umbenennen der Medien ist ein Fehler aufgetreten dataEditor.renamingMediaText={0} Mediendateien in {1} Ordnern wurden umbenannt. Bitte Speichern Sie den Vorgang. Andernfalls werden die Dateiumbennungen beim Schlie\u00DFen des Metadateneditors verworfen. @@ -848,6 +848,7 @@ numberVolumes=Anzahl der B\u00E4nde numberOfMetadata=Anzahl Metadaten objectType=Objekttyp ocr=OCR +ocrdWorkflow=OCR-D Workflow ok=Ok oldPassword=Altes Passwort onlySelectedPages=Nur die markierten Seiten @@ -899,6 +900,7 @@ position=Position ppn=PPN priority=Priorit\u00E4t process=Vorgang +processConfig.fromTemplate=aus Produktionsvorlage processData=Vorgangsdaten processDetails=Vorgangsdetails processList=Vorgangsliste @@ -964,6 +966,8 @@ removePhysicalDivision=Physisches Strukturelement entfernen removeRole=Rolle entfernen removeUserFromGroup=Benutzer aus der Benutzergruppe l\u00F6schen renameBatch=Batch umbenennen +renameMediaFiles=Medien umbenennen +renamingMediaFilesOfSelectedProcessesStarted=Das Umbenennen der Medien f\u00FCr die ausgew\u00E4hlten Vorg\u00E4nge wurde erfolgreich gestartet. requiredField=Mit * gekennzeichnete Felder sind Pflichtfelder reset=Zur\u00FCcksetzen resultPDF=Ergebnis-PDF @@ -1284,6 +1288,8 @@ assignTask=Aufgabe zuweisen overrideTask=Aufgabe \u00FCbernehmen superviseTask=Aufgabe beobachten renameMedia=Medien umbenennen +renameMediaThread=Medien umbenennen +renameMediaForProcessesConfirmMessage=Die Mediendateien von {0} Vorg\u00E4ngen werden gem\u00E4ss ihrer Reihenfolge in den jeweiligen Vorg\u00E4ngen umbenannt. Diese Aktion kann nicht r\u00FCckg\u00E4ngig gemacht werden. M\u00F6chten Sie fortfahren? resetWorkflow=Workflow zur\u00FCcksetzen runKitodoScript=KitodoScript ausf\u00FChren diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index 7b95ab6f49a..4d644e15063 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -59,6 +59,7 @@ ARCHIVED=Archived assigned=Assigned assignedTo=In edition by assignMedia=and assign selected media +assignOcrdWorkflow=Assign OCR-D workflow assignToNextElement=Assign to next element ats=ATS actions=Actions @@ -327,7 +328,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Select consecutive pages to dataEditor.selectMetadataTask=Select task dataEditor.layoutSavedSuccessfullyTitle=Current layout successfully saved dataEditor.layoutSavedSuccessfullyText=Your current editor settings have been saved successfully and will be applied to all future tasks of the same type. -dataEditor.renameMedia=Rename media dataEditor.renamingMediaComplete=Finished renaming media dataEditor.renamingMediaError=An error occurred while renaming media files dataEditor.renamingMediaText={0} media files in {1} folders have been renamed. Please click the 'Save' button to persist changes to the filenames. Otherwise the renaming will be reverted upon closing the editor. @@ -849,6 +849,7 @@ numberVolumes=Number of volumes numberOfMetadata=Number of metadata objectType=Object type ocr=OCR +ocrdWorkflow=OCR-D workflow ok=Ok oldPassword=Old password onlySelectedPages=Only the selected pages @@ -900,6 +901,7 @@ position=Position ppn=PPN priority=Priority process=Process +processConfig.fromTemplate=From process template processData=Process data processDetails=Process details processList=Process list @@ -965,6 +967,8 @@ removePhysicalDivision=Remove physical structure element removeRole=Remove role removeUserFromGroup=Delete user from group renameBatch=Rename batch +renameMediaFiles=Rename media +renamingMediaFilesOfSelectedProcessesStarted=Renaming media files of selected processes started successfully. requiredField=Fields marked with * are required reset=Reset resultPDF=Result PDF @@ -1286,6 +1290,8 @@ overrideTask=Take on task superviseTask=Watch task resetWorkflow=Reset workflow renameMedia=Rename media +renameMediaThread=Rename media +renameMediaForProcessesConfirmMessage=The media files of {0} processes will be renamed according to their order in the individual processes. This change cannot be reverted. Do you want to continue? runKitodoScript=Execute KitodoScript viewAllAuthorities=View all authorities diff --git a/Kitodo/src/main/resources/messages/messages_es.properties b/Kitodo/src/main/resources/messages/messages_es.properties index d958de4d5e7..930f88eae15 100644 --- a/Kitodo/src/main/resources/messages/messages_es.properties +++ b/Kitodo/src/main/resources/messages/messages_es.properties @@ -9,6 +9,12 @@ # GPL3-License.txt file that was distributed with this source code. # AND=Contiene +audioWaveformToolsCenteredCursor=Centrar cursor +audioWaveformToolsJumpForwardFiveSeconds=Adelantar 5 segundos +audioWaveformToolsJumpForwardOneSecond=Adelantar 1 segundo +audioWaveformToolsJumpBackFiveSeconds=Retroceder 5 segundos +audioWaveformToolsJumpBackOneSecond=Retroceder 1 segundo +audioWaveformToolsZoom=Zoom authorities=Autorizaciones authority=Autorización DMSExportByThread=La tarea se exporta desde el administrador de tareas al DMS\: @@ -317,7 +323,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Los elementos estructurales dataEditor.selectMetadataTask=Seleccionar la tarea dataEditor.layoutSavedSuccessfullyTitle=La plantilla actual se guardó correctamente dataEditor.layoutSavedSuccessfullyText=Su configuración actual del editor ha sido guardada con éxito y será aplicadad a todas las tareas futuras del mismo tipo. -dataEditor.renameMedia=Cambiar el nombre de los archivos de medios dataEditor.renamingMediaComplete=El cambio de nombre de los medios ha finalizado dataEditor.renamingMediaError=Se produjo un error al cambiar el nombre de los archivos multimedia dataEditor.renamingMediaText=Se ha cambiado el nombre de {0} archivos de medios en {1} carpeta. Por favor, haga clic en el botón 'Guardar' para mantener los cambios en los nombres de archivo. De lo contrario, el cambio de nombre se revertirá al cerrar el editor. @@ -465,6 +470,7 @@ folderUse.generatorSource=Utilizar como fuente para generar contenidos folderUse.generatorSource.disabled=Generación de contenido desactivada folderUse.mediaView=Usar para la vista multimedia folderUse.mediaView.disabled=Vista de medios desactivada +folderUse.audioMediaViewWaveform=Mostrar la forma de onda en la vista de medios para audio folderUse.preview=Utilizar como vista previa folderUse.preview.disabled=Vista previa deshabilitada folderUse.videoPreview=Usar como vista previa para videos @@ -951,6 +957,7 @@ removePhysicalDivision=Eliminar la posición de la estructura física removeRole=Eliminar el rol removeUserFromGroup=Eliminar un usuario del grupo de usuarios renameBatch=Cambiar el nombre del lote +renameMediaFiles=Cambiar el nombre de los archivos de medios requiredField=Los campos marcados con * son obligatorios reset=Restablecer resultPDF=Resultado PDF @@ -1082,7 +1089,7 @@ taskManagerIsRunning=No iniciar las tareas automáticamente taskManagerIsStopped=Inicie las tareas automáticamente taskManager=Gestor de tareas taskSaving=La tarea está guardada... -taskScriptThread=Tarea de guión +taskScriptThread=Tarea del script taskStatus=Estado de la tarea tasks=Tareas technicalData=Datos técnicos @@ -1272,6 +1279,7 @@ overrideTask=Asumir la tarea superviseTask=Observar la tarea resetWorkflow=Restablecer el flujo de trabajo renameMedia=Cambiar el nombre de los archivos multimedia +renameMediaThread=Cambiar el nombre de los archivos multimedia runKitodoScript=Ejecutar KitodoScript viewAllAuthorities=Mostrar todos los permisos diff --git a/Kitodo/src/main/resources/xslt/dfg-viewer.xsl b/Kitodo/src/main/resources/xslt/dfg-viewer.xsl index fe27b34bdb1..a38b30f33cc 100644 --- a/Kitodo/src/main/resources/xslt/dfg-viewer.xsl +++ b/Kitodo/src/main/resources/xslt/dfg-viewer.xsl @@ -11,13 +11,135 @@ * GPL3-License.txt file that was distributed with this source code. * --> - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + physSequence + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/header.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/header.xhtml index 2d14e19728c..6c2afa2a238 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/header.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/header.xhtml @@ -70,7 +70,7 @@ @@ -46,6 +47,7 @@ + @@ -80,6 +82,22 @@ + +
+ + + + + + +
diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/errorPopup.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/errorPopup.xhtml new file mode 100644 index 00000000000..5687ed83717 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/errorPopup.xhtml @@ -0,0 +1,49 @@ + + + + + +

#{err.errorOccurred}

+
+ #{ProcessForm.errorMessage}
+
+
+ + + + + + + + + + +
+
diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/processList.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/processList.xhtml index a59ca02c4e0..d08d64479a9 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/processList.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/processList.xhtml @@ -317,6 +317,11 @@ action="#{ProcessForm.generateResultAsPdf}" ajax="false" icon="fa fa-file-pdf-o"/> + diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/renameMediaConfirmDialog.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/renameMediaConfirmDialog.xhtml new file mode 100644 index 00000000000..0d022b0deb3 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processes/renameMediaConfirmDialog.xhtml @@ -0,0 +1,59 @@ + + + + + + +

#{msgs.renameMedia}

+ + + +
+ +
+
+ + + + + + +
+
+
+
+
diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/roleEdit/details.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/roleEdit/details.xhtml index 34752a6435e..503a33fe4f9 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/roleEdit/details.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/roleEdit/details.xhtml @@ -42,52 +42,53 @@
-
- - - - -

- -

- - #{msgs.available} - #{msgs.assigned} - -
+ + + + +

+ +

+ + #{msgs.available} + #{msgs.assigned} + +
+
- - -

- -

- - #{msgs.available} - #{msgs.assigned} - -
-
-
+ + + +

+ +

+ + #{msgs.available} + #{msgs.assigned} + +
+
+ diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/templateEdit/details.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/templateEdit/details.xhtml index 73cd95a1402..88005b27ae3 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/templateEdit/details.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/templateEdit/details.xhtml @@ -13,6 +13,7 @@ @@ -95,6 +96,21 @@ +
+ + + + + + +
diff --git a/Kitodo/src/main/webapp/pages/processes.xhtml b/Kitodo/src/main/webapp/pages/processes.xhtml index df3c53b05d0..a08b8dab413 100644 --- a/Kitodo/src/main/webapp/pages/processes.xhtml +++ b/Kitodo/src/main/webapp/pages/processes.xhtml @@ -97,5 +97,7 @@ + + diff --git a/Kitodo/src/test/java/org/kitodo/MockDatabase.java b/Kitodo/src/test/java/org/kitodo/MockDatabase.java index 0451516197a..f3f956a78b5 100644 --- a/Kitodo/src/test/java/org/kitodo/MockDatabase.java +++ b/Kitodo/src/test/java/org/kitodo/MockDatabase.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.sql.Date; @@ -58,7 +57,6 @@ import org.kitodo.api.externaldatamanagement.SearchInterfaceType; import org.kitodo.api.schemaconverter.FileFormat; import org.kitodo.api.schemaconverter.MetadataFormat; -import org.kitodo.config.ConfigCore; import org.kitodo.config.ConfigMain; import org.kitodo.data.database.beans.Authority; import org.kitodo.data.database.beans.Batch; @@ -125,7 +123,6 @@ public class MockDatabase { public static final String MEDIA_REFERENCES_TEST_PROCESS_TITLE = "Media"; public static final String METADATA_LOCK_TEST_PROCESS_TITLE = "Metadata lock"; public static final String MEDIA_RENAMING_TEST_PROCESS_TITLE = "Rename media"; - public static final String META_XML = "/meta.xml"; public static void startDatabaseServer() throws SQLException { tcpServer = Server.createTcpServer().start(); @@ -1117,7 +1114,7 @@ public static int insertTestProcessForRenamingMediaTestIntoSecondProject() throw * @throws DAOException when loading test project fails * @throws DataException when saving test process fails */ - private static int insertTestProcessIntoSecondProject(String processTitle) throws DAOException, DataException { + public static int insertTestProcessIntoSecondProject(String processTitle) throws DAOException, DataException { Project projectTwo = ServiceManager.getProjectService().getById(2); Template template = projectTwo.getTemplates().get(0); Process mediaReferencesProcess = new Process(); @@ -2101,24 +2098,4 @@ public static Process addProcess(String processTitle, int projectId, int templat ServiceManager.getProcessService().save(process); return process; } - - /** - * Copy test metadata xml file with provided 'filename' to process directory of process with provided ID - * 'processId'. Creates directory if it does not exist. - * @param processId process ID - * @param filename filename of metadata file - * @throws IOException when subdirectory cannot be created or metadata file cannot be copied - */ - public static void copyTestMetadataFile(int processId, String filename) throws IOException { - URI processDir = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)) - .toUri(); - URI processDirTargetFile = Paths.get(ConfigCore.getKitodoDataDirectory(), processId - + META_XML).toUri(); - URI metaFileUri = Paths.get(ConfigCore.getKitodoDataDirectory(), filename).toUri(); - if (!ServiceManager.getFileService().isDirectory(processDir)) { - ServiceManager.getFileService().createDirectory(Paths.get(ConfigCore.getKitodoDataDirectory()).toUri(), - String.valueOf(processId)); - } - ServiceManager.getFileService().copyFile(metaFileUri, processDirTargetFile); - } } diff --git a/Kitodo/src/test/java/org/kitodo/production/helper/VariableReplacerTest.java b/Kitodo/src/test/java/org/kitodo/production/helper/VariableReplacerTest.java index de8cee8174e..403fbaf4b24 100644 --- a/Kitodo/src/test/java/org/kitodo/production/helper/VariableReplacerTest.java +++ b/Kitodo/src/test/java/org/kitodo/production/helper/VariableReplacerTest.java @@ -23,6 +23,7 @@ import org.kitodo.data.database.beans.Process; import org.kitodo.data.database.beans.Project; import org.kitodo.data.database.beans.Ruleset; +import org.kitodo.data.database.beans.Template; public class VariableReplacerTest { @@ -159,6 +160,26 @@ public void shouldReplaceGeneratorSourcePath() { replaced); } + @Test + public void shouldReplaceOcrdWorkflowId() { + Process process = prepareProcess(); + Template template = new Template(); + template.setOcrdWorkflowId("/template-ocrd-workflow.sh"); + process.setTemplate(template); + + VariableReplacer variableReplacerTemplate = new VariableReplacer(null, process, null); + String replaced = variableReplacerTemplate.replace("-title (ocrdworkflowid) -hardcoded test"); + String expected = "-title " + template.getOcrdWorkflowId() + " -hardcoded test"; + assertEquals("String was replaced incorrectly!", expected, replaced); + + process.setOcrdWorkflowId("/process-ocrd-workflow.sh"); + VariableReplacer variableReplacerProcess = new VariableReplacer(null, process, null); + replaced = variableReplacerProcess.replace("-title (ocrdworkflowid) -hardcoded test"); + expected = "-title " + process.getOcrdWorkflowId() + " -hardcoded test"; + assertEquals("String was replaced incorrectly!", expected, replaced); + } + + private Process prepareProcess() { Process process = new Process(); process.setId(2); diff --git a/Kitodo/src/test/java/org/kitodo/production/services/file/FileServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/file/FileServiceIT.java index de257219b2b..eb6a935ceb2 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/file/FileServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/file/FileServiceIT.java @@ -21,25 +21,55 @@ import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; - +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import org.kitodo.MockDatabase; import org.kitodo.TreeDeleter; +import org.kitodo.api.dataformat.MediaVariant; +import org.kitodo.api.dataformat.PhysicalDivision; +import org.kitodo.api.dataformat.Workpiece; import org.kitodo.config.ConfigCore; import org.kitodo.config.enums.ParameterCore; +import org.kitodo.data.database.beans.Process; +import org.kitodo.data.database.exceptions.DAOException; +import org.kitodo.data.exceptions.DataException; +import org.kitodo.production.helper.metadata.ImageHelper; import org.kitodo.production.services.ServiceManager; +import org.kitodo.production.services.data.ProcessService; +import org.kitodo.production.thread.RenameMediaThread; +import org.kitodo.utils.ProcessTestUtils; public class FileServiceIT { /** * One of the directories that are generated by the test and later deleted. */ private static final String SEVERAL = "several"; + private static final String TEST_RENAME_MEDIA_FILE = "testRenameMediaMeta.xml"; + private static final String RENAME_MEDIA_PROCESS_1 = "renameMediaProcess1"; + private static final String RENAME_MEDIA_PROCESS_2 = "renameMediaProcess2"; + private static final String RENAME_MEDIA_REVERT_PROCESS = "revertMediaRenamingProcess"; + private static List dummyProcessIds = new LinkedList<>(); + private static int mediaRenamingFirstProcessId = -1; + private static int mediaRenamingSecondProcessId = -1; + private static int revertMediaRenamingProcessId = -1; @BeforeClass - public static void setUp() throws IOException { + public static void setUp() throws Exception { FileService fileService = new FileService(); fileService.createDirectory(URI.create(""), "fileServiceTest"); + MockDatabase.startNode(); + MockDatabase.insertProcessesFull(); + MockDatabase.startDatabaseServer(); + MockDatabase.insertFoldersForSecondProject(); } @AfterClass @@ -107,9 +137,101 @@ public void testMetadataImageComparator() { } + @Test + public void testRenamingOfMultipleProcesses() throws DAOException, DataException, IOException, InterruptedException { + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); + mediaRenamingFirstProcessId = MockDatabase.insertTestProcessIntoSecondProject(RENAME_MEDIA_PROCESS_1); + ProcessTestUtils.insertDummyProcesses(); + mediaRenamingSecondProcessId = MockDatabase.insertTestProcessIntoSecondProject(RENAME_MEDIA_PROCESS_2); + ProcessTestUtils.copyTestFiles(mediaRenamingFirstProcessId, TEST_RENAME_MEDIA_FILE); + ProcessTestUtils.copyTestFiles(mediaRenamingSecondProcessId, TEST_RENAME_MEDIA_FILE); + List testProcesses = new LinkedList<>(); + testProcesses.add(ServiceManager.getProcessService().getById(mediaRenamingFirstProcessId)); + testProcesses.add(ServiceManager.getProcessService().getById(mediaRenamingSecondProcessId)); + // 1. check filename & order values of both processes _before_ renaming + assertFalse(mediaFilesNamedAccordingToOrderAttribute(mediaRenamingFirstProcessId)); + assertFalse(mediaFilesNamedAccordingToOrderAttribute(mediaRenamingSecondProcessId)); + // 2. perform media renaming + RenameMediaThread renameMediaThread = new RenameMediaThread(testProcesses); + renameMediaThread.start(); + renameMediaThread.join(3000); + // 3. check filename & order values of both processes _after_ renaming + assertTrue(mediaFilesNamedAccordingToOrderAttribute(mediaRenamingFirstProcessId)); + assertTrue(mediaFilesNamedAccordingToOrderAttribute(mediaRenamingSecondProcessId)); + } + + @Test + public void testRevertingOriginalFilenamesAfterRenamingError() throws DAOException, DataException, IOException, + InterruptedException { + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); + revertMediaRenamingProcessId = MockDatabase.insertTestProcessIntoSecondProject(RENAME_MEDIA_REVERT_PROCESS); + ProcessTestUtils.copyTestFiles(revertMediaRenamingProcessId, TEST_RENAME_MEDIA_FILE); + Path processScansDir = Paths.get(ConfigCore.getKitodoDataDirectory(), revertMediaRenamingProcessId + + "/images/scans/").toAbsolutePath(); + // 1. delete last referenced image to provoke failed media renaming attempt + Path lastImagePath = processScansDir.resolve("03.tif"); + ServiceManager.getFileService().delete(lastImagePath.toUri()); + // 2. determine filenames before failed media renaming attempt + List scanURIsBeforeRenaming = ServiceManager.getFileService().getSubUris(ImageHelper.dataFilter, + processScansDir.toUri()).stream().sorted().collect(Collectors.toList()); + // 3. perform failing media renaming + Process process = ServiceManager.getProcessService().getById(revertMediaRenamingProcessId); + RenameMediaThread renameMediaThread = new RenameMediaThread(Collections.singletonList(process)); + renameMediaThread.start(); + renameMediaThread.join(3000); + // 4. determine filenames after failed media renaming attempt + List scanURIsAfterRenaming = ServiceManager.getFileService().getSubUris(ImageHelper.dataFilter, + processScansDir.toUri()).stream().sorted().collect(Collectors.toList()); + // 5. assert that filenames have been successfully reverted to originals after failed media renaming attempt + assertEquals(scanURIsBeforeRenaming.size(), scanURIsAfterRenaming.size()); + for (int i = 0; i < scanURIsBeforeRenaming.size(); i++) { + assertEquals(scanURIsBeforeRenaming.get(i).toString(), scanURIsAfterRenaming.get(i).toString()); + } + } + + private boolean mediaFilesNamedAccordingToOrderAttribute(int processId) throws DAOException, IOException { + Process process = ServiceManager.getProcessService().getById(processId); + int filenameLength = process.getProject().getFilenameLength(); + for (PhysicalDivision page : getProcessPages(process)) { + for (Map.Entry mediaVariantURIEntry : page.getMediaFiles().entrySet()) { + String basename = FilenameUtils.getBaseName(mediaVariantURIEntry.getValue().getPath()); + String expectedName = StringUtils.leftPad(String.valueOf(page.getOrder()), filenameLength, '0'); + if (!(Integer.parseInt(basename) == page.getOrder() && basename.equals(expectedName))) { + return false; + } + } + } + return true; + } + + private static List getProcessPages(Process process) throws IOException { + URI uri = ServiceManager.getProcessService().getMetadataFileUri(process); + Workpiece workpiece = ServiceManager.getMetsService().loadWorkpiece(uri); + return workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); + } + private void cleanUp() throws IOException { Path testBaseDirectoryPath = Paths.get(ConfigCore.getParameter(ParameterCore.DIR_PROCESSES)); Path firstDirectoryOfTestPath = testBaseDirectoryPath.resolve(SEVERAL); TreeDeleter.deltree(firstDirectoryOfTestPath); } + + /** + * Cleanup test resources and dummy processes after test completion. + * + * @throws Exception when removing process from database, index or filesystem fails. + */ + @AfterClass + public static void removeDummyAndTestProcesses() throws Exception { + for (int processId : dummyProcessIds) { + ServiceManager.getProcessService().removeFromDatabase(processId); + ServiceManager.getProcessService().removeFromIndex(processId, false); + } + dummyProcessIds = new LinkedList<>(); + ProcessService.deleteProcess(mediaRenamingFirstProcessId); + ProcessService.deleteProcess(mediaRenamingSecondProcessId); + ProcessService.deleteProcess(revertMediaRenamingProcessId); + MockDatabase.stopNode(); + MockDatabase.cleanDatabase(); + } } diff --git a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java index bdb0a96c7ac..bee07e99446 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java @@ -37,6 +37,7 @@ import org.kitodo.selenium.testframework.pages.ProcessFromTemplatePage; import org.kitodo.selenium.testframework.pages.ProcessesPage; import org.kitodo.selenium.testframework.pages.ProjectsPage; +import org.kitodo.utils.ProcessTestUtils; import org.openqa.selenium.support.ui.Select; public class ImportingST extends BaseTestSelenium { @@ -120,7 +121,7 @@ public void checkSearchButtonActivatedText() throws Exception { @Test public void checkDefaultChildProcessImportConfiguration() throws Exception { Process process = ServiceManager.getProcessService().getById(multiVolumeWorkId); - MockDatabase.copyTestMetadataFile(multiVolumeWorkId, TEST_MULTI_VOLUME_WORK_FILE); + ProcessTestUtils.copyTestMetadataFile(multiVolumeWorkId, TEST_MULTI_VOLUME_WORK_FILE); // re-save test process to ensure correct baseType ServiceManager.getProcessService().save(process, true); processesPage.goTo(); diff --git a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java index 4b93464daf3..468b70540dd 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java @@ -16,14 +16,11 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; import org.junit.After; import org.junit.AfterClass; @@ -42,13 +39,13 @@ import org.kitodo.selenium.testframework.BaseTestSelenium; import org.kitodo.selenium.testframework.Browser; import org.kitodo.selenium.testframework.Pages; +import org.kitodo.utils.ProcessTestUtils; /** * Tests for functions in the metadata editor. */ public class MetadataST extends BaseTestSelenium { - private static final String TEST_IMAGES_DIR = "images"; private static final String TEST_MEDIA_REFERENCES_FILE = "testUpdatedMediaReferencesMeta.xml"; private static final String TEST_METADATA_LOCK_FILE = "testMetadataLockMeta.xml"; private static final String TEST_RENAME_MEDIA_FILE = "testRenameMediaMeta.xml"; @@ -65,32 +62,30 @@ public class MetadataST extends BaseTestSelenium { private static final String FIRST_CHILD_ID = "FIRST_CHILD_ID"; private static final String SECOND_CHILD_ID = "SECOND_CHILD_ID"; private static List processHierarchyTestProcessIds = new LinkedList<>(); - private static final int TEST_PROJECT_ID = 1; - private static final int TEST_TEMPLATE_ID = 1; private static final String FIRST_STRUCTURE_TREE_NODE_LABEL = "1 : -"; private static final String SECOND_STRUCTURE_TREE_NODE_LABEL = "2 : -"; private static void prepareMediaReferenceProcess() throws DAOException, DataException, IOException { - insertDummyProcesses(); + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); insertTestProcessForMediaReferencesTest(); copyTestFilesForMediaReferences(); } private static void prepareMetadataLockProcess() throws DAOException, DataException, IOException { - insertDummyProcesses(); + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); insertTestProcessForMetadataLockTest(); - MockDatabase.copyTestMetadataFile(metadataLockProcessId, TEST_METADATA_LOCK_FILE); + ProcessTestUtils.copyTestMetadataFile(metadataLockProcessId, TEST_METADATA_LOCK_FILE); } private static void prepareProcessHierarchyProcesses() throws DAOException, IOException, DataException { - insertDummyProcesses(); + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); processHierarchyTestProcessIds = linkProcesses(); copyTestParentProcessMetadataFile(); updateChildProcessIdsInParentProcessMetadataFile(); } private static void prepareMediaRenamingProcess() throws DAOException, DataException, IOException { - insertDummyProcesses(); + dummyProcessIds = ProcessTestUtils.insertDummyProcesses(); insertTestProcessForRenamingMediaFiles(); copyTestFilesForRenamingMediaFiles(); } @@ -296,9 +291,9 @@ private static void insertTestProcessForRenamingMediaFiles() throws DAOException private static List linkProcesses() throws DAOException, DataException { List processIds = new LinkedList<>(); List childProcesses = new LinkedList<>(); - childProcesses.add(addProcess(FIRST_CHILD_PROCESS_TITLE)); - childProcesses.add(addProcess(SECOND_CHILD_PROCESS_TITLE)); - Process parentProcess = addProcess(PARENT_PROCESS_TITLE); + childProcesses.add(ProcessTestUtils.addProcess(FIRST_CHILD_PROCESS_TITLE)); + childProcesses.add(ProcessTestUtils.addProcess(SECOND_CHILD_PROCESS_TITLE)); + Process parentProcess = ProcessTestUtils.addProcess(PARENT_PROCESS_TITLE); parentProcess.getChildren().addAll(childProcesses); ServiceManager.getProcessService().save(parentProcess); parentProcessId = parentProcess.getId(); @@ -311,62 +306,21 @@ private static List linkProcesses() throws DAOException, DataException return processIds; } - private static Process addProcess(String processTitle) throws DAOException, DataException { - insertDummyProcesses(); - return MockDatabase.addProcess(processTitle, TEST_PROJECT_ID, TEST_TEMPLATE_ID); - } - - private static void insertDummyProcesses() throws DAOException, DataException { - dummyProcessIds = new LinkedList<>(); - List processIds = ServiceManager.getProcessService().getAll().stream().map(Process::getId) - .collect(Collectors.toList()); - int id = Collections.max(processIds) + 1; - while (processDirExists(id)) { - dummyProcessIds.add(MockDatabase.insertDummyProcess(id)); - id++; - } - } - - private static boolean processDirExists(int processId) { - URI uri = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)).toUri(); - return ServiceManager.getFileService().isDirectory(uri); - } - private static void copyTestFilesForMediaReferences() throws IOException { - copyTestFiles(mediaReferencesProcessId, TEST_MEDIA_REFERENCES_FILE); + ProcessTestUtils.copyTestFiles(mediaReferencesProcessId, TEST_MEDIA_REFERENCES_FILE); } private static void copyTestFilesForRenamingMediaFiles() throws IOException { - copyTestFiles(renamingMediaProcessId, TEST_RENAME_MEDIA_FILE); - } - - private static void copyTestFiles(int processId, String filename) throws IOException { - // copy test meta xml - MockDatabase.copyTestMetadataFile(processId, filename); - URI processDir = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)) - .toUri(); - // copy test images - URI testImagesUri = Paths.get(ConfigCore.getKitodoDataDirectory(), TEST_IMAGES_DIR).toUri(); - URI targetImages = Paths.get(ConfigCore.getKitodoDataDirectory(), processId - + "/images/").toUri(); - try { - if (!ServiceManager.getFileService().isDirectory(targetImages)) { - ServiceManager.getFileService().createDirectory(processDir, TEST_IMAGES_DIR); - } - ServiceManager.getFileService().copyDirectory(testImagesUri, targetImages); - } catch (IOException e) { - e.printStackTrace(); - throw e; - } + ProcessTestUtils.copyTestFiles(renamingMediaProcessId, TEST_RENAME_MEDIA_FILE); } private static void copyTestParentProcessMetadataFile() throws IOException { - MockDatabase.copyTestMetadataFile(parentProcessId, TEST_PARENT_PROCESS_METADATA_FILE); + ProcessTestUtils.copyTestMetadataFile(parentProcessId, TEST_PARENT_PROCESS_METADATA_FILE); } private static void updateChildProcessIdsInParentProcessMetadataFile() throws IOException, DAOException { Process parentProcess = ServiceManager.getProcessService().getById(parentProcessId); - Path metaXml = Paths.get(ConfigCore.getKitodoDataDirectory(), parentProcessId + MockDatabase.META_XML); + Path metaXml = Paths.get(ConfigCore.getKitodoDataDirectory(), parentProcessId + ProcessTestUtils.META_XML); String xmlContent = Files.readString(metaXml); String firstChildId = String.valueOf(parentProcess.getChildren().get(0).getId()); String secondChildId = String.valueOf(parentProcess.getChildren().get(1).getId()); diff --git a/Kitodo/src/test/java/org/kitodo/utils/ProcessTestUtils.java b/Kitodo/src/test/java/org/kitodo/utils/ProcessTestUtils.java index 9c46bdc8348..113c65e12a8 100644 --- a/Kitodo/src/test/java/org/kitodo/utils/ProcessTestUtils.java +++ b/Kitodo/src/test/java/org/kitodo/utils/ProcessTestUtils.java @@ -14,13 +14,35 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.MockDatabase; import org.kitodo.api.MetadataEntry; import org.kitodo.api.dataeditor.rulesetmanagement.SimpleMetadataViewInterface; +import org.kitodo.config.ConfigCore; +import org.kitodo.data.database.beans.Process; +import org.kitodo.data.database.exceptions.DAOException; +import org.kitodo.data.exceptions.DataException; import org.kitodo.production.forms.createprocess.ProcessTextMetadata; +import org.kitodo.production.services.ServiceManager; import org.primefaces.model.DefaultTreeNode; public class ProcessTestUtils { + private static final String TEST_IMAGES_DIR = "images"; + public static final String META_XML = "/meta.xml"; + private static final int TEST_PROJECT_ID = 1; + private static final int TEST_TEMPLATE_ID = 1; + private static final Logger logger = LogManager.getLogger(ProcessTestUtils.class); + /** * Get a tree node with ProcessTextMetadata item. * @@ -47,4 +69,87 @@ private static SimpleMetadataViewInterface getSimpleMetadataView(String metadata return simpleMetadataView; } + /** + * Copy metadata test file with provided filename "filename" to process directory of test process with ID + * "processId". Additionally, copy test images to said test processes image directory. + * @param processId ID of test process to whose process directory test files are copied + * @param filename filename of metadata file to copy + * @throws IOException when copying test metadata file fails + */ + public static void copyTestFiles(int processId, String filename) throws IOException { + // copy test meta xml + copyTestMetadataFile(processId, filename); + URI processDir = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)) + .toUri(); + // copy test images + URI testImagesUri = Paths.get(ConfigCore.getKitodoDataDirectory(), TEST_IMAGES_DIR).toUri(); + URI targetImages = Paths.get(ConfigCore.getKitodoDataDirectory(), processId + + "/" + TEST_IMAGES_DIR + "/").toUri(); + try { + if (!ServiceManager.getFileService().isDirectory(targetImages)) { + ServiceManager.getFileService().createDirectory(processDir, TEST_IMAGES_DIR); + } + ServiceManager.getFileService().copyDirectory(testImagesUri, targetImages); + } catch (IOException e) { + logger.error(e.getMessage()); + throw e; + } + } + + /** + * Copy test metadata xml file with provided 'filename' to process directory of process with provided ID + * 'processId'. Creates directory if it does not exist. + * @param processId process ID + * @param filename filename of metadata file + * @throws IOException when subdirectory cannot be created or metadata file cannot be copied + */ + public static void copyTestMetadataFile(int processId, String filename) throws IOException { + URI processDir = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)) + .toUri(); + URI processDirTargetFile = Paths.get(ConfigCore.getKitodoDataDirectory(), processId + + META_XML).toUri(); + URI metaFileUri = Paths.get(ConfigCore.getKitodoDataDirectory(), filename).toUri(); + if (!ServiceManager.getFileService().isDirectory(processDir)) { + ServiceManager.getFileService().createDirectory(Paths.get(ConfigCore.getKitodoDataDirectory()).toUri(), + String.valueOf(processId)); + } + ServiceManager.getFileService().copyFile(metaFileUri, processDirTargetFile); + } + + /** + * Add process with given title "processTitle" and to project with configured ID 'TEST_PROJECT_ID' and template + * 'TEST_TEMPLATE_ID' to mock database. + * @param processTitle title of process to add + * @return created process + * @throws DAOException when adding process fails + * @throws DataException when adding process fails + */ + public static Process addProcess(String processTitle) throws DAOException, DataException { + insertDummyProcesses(); + return MockDatabase.addProcess(processTitle, TEST_PROJECT_ID, TEST_TEMPLATE_ID); + } + + /** + * Insert dummy processes into database to avoid conflicts with existing test processes and process directories with + * static identifiers. + * @return list of dummy process IDs + * @throws DAOException when retrieving existing processes from or inserting dummy processes into database fails + * @throws DataException when inserting dummy processes into database fails + */ + public static List insertDummyProcesses() throws DAOException, DataException { + List dummyProcessIds = new LinkedList<>(); + List processIds = ServiceManager.getProcessService().getAll().stream().map(Process::getId) + .collect(Collectors.toList()); + int id = Collections.max(processIds) + 1; + while (processDirExists(id)) { + dummyProcessIds.add(MockDatabase.insertDummyProcess(id)); + id++; + } + return dummyProcessIds; + } + + private static boolean processDirExists(int processId) { + URI uri = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(processId)).toUri(); + return ServiceManager.getFileService().isDirectory(uri); + } }