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..cd35782beb2 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java @@ -1182,4 +1182,11 @@ private int getProcessId(Object process) { public FilterMenu getFilterMenu() { return filterMenu; } + + /** + * Rename media files of all selected processes. + */ + public void renameMedia() { + ServiceManager.getFileService().renameMedia(getSelectedProcesses()); + } } 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 b258f24d26f..ed61476f490 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; public class FileService { @@ -1515,6 +1520,37 @@ public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMa return numberOfRenamedMedia; } + /** + * 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 @@ -1525,6 +1561,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()) { @@ -1536,8 +1573,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/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/messages/errors_de.properties b/Kitodo/src/main/resources/messages/errors_de.properties index 7fd7b25ae37..04e5d680245 100644 --- a/Kitodo/src/main/resources/messages/errors_de.properties +++ b/Kitodo/src/main/resources/messages/errors_de.properties @@ -126,6 +126,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}''). diff --git a/Kitodo/src/main/resources/messages/errors_en.properties b/Kitodo/src/main/resources/messages/errors_en.properties index 39434cbf6c7..655512e16ae 100644 --- a/Kitodo/src/main/resources/messages/errors_en.properties +++ b/Kitodo/src/main/resources/messages/errors_en.properties @@ -126,6 +126,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}''). diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index a3946368762..79f52ca32a5 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -325,7 +325,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. @@ -961,6 +960,7 @@ removePhysicalDivision=Physisches Strukturelement entfernen removeRole=Rolle entfernen removeUserFromGroup=Benutzer aus der Benutzergruppe l\u00F6schen renameBatch=Batch umbenennen +renameMediaFiles=Medien umbenennen requiredField=Mit * gekennzeichnete Felder sind Pflichtfelder reset=Zur\u00FCcksetzen resultPDF=Ergebnis-PDF @@ -1281,6 +1281,7 @@ assignTask=Aufgabe zuweisen overrideTask=Aufgabe \u00FCbernehmen superviseTask=Aufgabe beobachten renameMedia=Medien umbenennen +renameMediaThread=Medien umbenennen 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 c27732562d1..8f9907a509f 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -325,7 +325,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. @@ -962,6 +961,7 @@ removePhysicalDivision=Remove physical structure element removeRole=Remove role removeUserFromGroup=Delete user from group renameBatch=Rename batch +renameMediaFiles=Rename media requiredField=Fields marked with * are required reset=Reset resultPDF=Result PDF @@ -1283,6 +1283,7 @@ overrideTask=Take on task superviseTask=Watch task resetWorkflow=Reset workflow renameMedia=Rename media +renameMediaThread=Rename media 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..a59e5a0f7e5 100644 --- a/Kitodo/src/main/resources/messages/messages_es.properties +++ b/Kitodo/src/main/resources/messages/messages_es.properties @@ -317,7 +317,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. @@ -951,6 +950,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 @@ -1272,6 +1272,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/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 @@ + 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/services/file/FileServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/file/FileServiceIT.java index de257219b2b..139163a8ed1 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,96 @@ 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(); + // 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(); + // 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.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + } + private void cleanUp() throws IOException { Path testBaseDirectoryPath = Paths.get(ConfigCore.getParameter(ParameterCore.DIR_PROCESSES)); Path firstDirectoryOfTestPath = testBaseDirectoryPath.resolve(SEVERAL); TreeDeleter.deltree(firstDirectoryOfTestPath); } + + @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..5a35a617022 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 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; +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; + 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); + } }