diff --git a/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java b/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java index 14126ab3fdb..797a06a59ed 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java @@ -12,6 +12,7 @@ package org.kitodo.api.dataformat; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; @@ -187,9 +188,20 @@ public List getAllLogicalDivisions() { * @return all physical divisions with type "page", sorted by their {@code order} */ public List getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted() { + return getAllPhysicalDivisionChildrenFilteredByTypes(Collections.singletonList(PhysicalDivision.TYPE_PAGE)); + } + + /** + * Returns all child physical divisions of the physical division of the workpiece with any of the types in the + * provided list "types". + * + * @param types list of types + * @return child physical division of given types + */ + public List getAllPhysicalDivisionChildrenFilteredByTypes(List types) { return physicalStructure.getChildren().stream() .flatMap(Workpiece::treeStream) - .filter(division -> Objects.equals(division.getType(), PhysicalDivision.TYPE_PAGE)) + .filter(physicalDivisionToCheck -> types.contains(physicalDivisionToCheck.getType())) .sorted(Comparator.comparing(PhysicalDivision::getOrder)).collect(Collectors.toUnmodifiableList()); } diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java index 673fe6df37e..faf64e45948 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java @@ -156,6 +156,12 @@ public class Project extends BaseIndexedBean implements Comparable { @JoinColumn(name = "mediaView_video_folder_id", foreignKey = @ForeignKey(name = "FK_project_mediaView_video_folder_id")) private Folder videoMediaView; + /** + * Filename length for renaming media files of processes in this project. + */ + @Column(name = "filename_length") + private Integer filenameLength; + /** * Constructor. */ @@ -621,6 +627,25 @@ public void setDefaultChildProcessImportConfiguration(ImportConfiguration defaul this.defaultChildProcessImportConfiguration = defaultChildProcessImportConfiguration; } + /** + * Get filename length. + * @return filename length + */ + public Integer getFilenameLength() { + if (Objects.isNull(filenameLength)) { + filenameLength = 8; + } + return filenameLength; + } + + /** + * Set filename length. + * @param filenameLength as Integer + */ + public void setFilenameLength(Integer filenameLength) { + this.filenameLength = filenameLength; + } + @Override public int compareTo(Project project) { return this.getTitle().compareTo(project.getTitle()); diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_124__Add_permission_to_rename_media.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_124__Add_permission_to_rename_media.sql new file mode 100644 index 00000000000..69df16c74c5 --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_124__Add_permission_to_rename_media.sql @@ -0,0 +1,21 @@ +-- +-- (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. +-- + +SET SQL_SAFE_UPDATES = 0; + +-- add authorities/permission for renaming media files +INSERT IGNORE INTO authority (title) VALUES ('renameMedia_clientAssignable'); +INSERT IGNORE INTO authority (title) VALUES ('renameMedia_globalAssignable'); + +-- add "filenameLength" column to project table +ALTER TABLE project ADD filename_length INT DEFAULT 8; + +SET SQL_SAFE_UPDATES = 1; 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 a93fd26fed4..032f46ef4ba 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java @@ -1081,4 +1081,13 @@ public boolean hasAuthorityToViewDatabaseStatistics() { public boolean hasAuthorityToRunKitodoScripts() { return securityAccessService.hasAuthorityToRunKitodoScripts(); } + + /** + * Check if the current user has the permission to rename media files. + * + * @return true if the current user has the permission to rename media files. + */ + public boolean hasAuthorityToRenameMediaFiles() { + return securityAccessService.hasAuthorityToRenameMediaFiles(); + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/DataEditorForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/DataEditorForm.java index 7b81641249f..8bcf5eddfa8 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/DataEditorForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/DataEditorForm.java @@ -15,6 +15,7 @@ import java.io.OutputStream; import java.io.Serializable; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; @@ -34,6 +35,7 @@ import javax.inject.Inject; import javax.inject.Named; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -202,6 +204,12 @@ public class DataEditorForm implements MetadataTreeTableInterface, RulesetSetupI private MediaProvider mediaProvider; private boolean mediaUpdated = false; + private DualHashBidiMap filenameMapping = new DualHashBidiMap<>(); + + private int numberOfNewMappings = 0; + + private String renamingError = ""; + /** * Public constructor. */ @@ -382,6 +390,7 @@ public String closeAndReturn() { public void close() { deleteNotSavedUploadedMedia(); unsavedDeletedMedia.clear(); + ServiceManager.getFileService().revertRenaming(filenameMapping.inverseBidiMap(), workpiece); metadataPanel.clear(); structurePanel.clear(); workpiece = null; @@ -431,6 +440,8 @@ private void deleteNotSavedUploadedMedia() { ServiceManager.getProcessService().getProcessDataDirectory(this.process).getPath()).toUri(); for (PhysicalDivision mediaUnit : this.unsavedUploadedMedia) { for (URI fileURI : mediaUnit.getMediaFiles().values()) { + this.filenameMapping = ServiceManager.getFileService() + .removeUnsavedUploadMediaUriFromFileMapping(fileURI, this.filenameMapping); try { ServiceManager.getFileService().delete(uri.resolve(fileURI)); } catch (IOException e) { @@ -488,6 +499,8 @@ private String save(boolean close) { try { metadataPanel.preserve(); structurePanel.preserve(); + // reset "image filename renaming map" so nothing is reverted after saving! + filenameMapping = new DualHashBidiMap<>(); ServiceManager.getProcessService().updateChildrenFromLogicalStructure(process, workpiece.getLogicalStructure()); ServiceManager.getFileService().createBackupFile(process); try (OutputStream out = ServiceManager.getFileService().write(mainFileUri)) { @@ -1100,4 +1113,45 @@ public boolean isMediaUpdated() { public void setMediaUpdated(boolean mediaUpdated) { this.mediaUpdated = mediaUpdated; } + + /** + * Rename media files of current process according to their corresponding physical divisions ORDER attribute. + */ + public void renameMediaFiles() { + renamingError = ""; + try { + numberOfNewMappings = ServiceManager.getFileService().renameMediaFiles(process, workpiece, filenameMapping); + } catch (IOException | URISyntaxException e) { + renamingError = e.getMessage(); + } + showPanels(); + if (StringUtils.isBlank(renamingError)) { + PrimeFaces.current().executeScript("PF('renamingMediaSuccessDialog').show();"); + } else { + PrimeFaces.current().executeScript("PF('renamingMediaErrorDialog').show();"); + } + } + + /** + * Get renamingError. + * @return renamingError + */ + public String getRenamingError() { + return renamingError; + } + + /** + * Return renaming success message containing number of renamed media files and configured sub-folders. + * @return renaming success message + */ + public String getRenamingSuccessMessage() { + return Helper.getTranslation("dataEditor.renamingMediaText", String.valueOf(numberOfNewMappings), + String.valueOf(process.getProject().getFolders().size())); + } + + private void showPanels() { + galleryPanel.show(); + paginationPanel.show(); + structurePanel.show(); + } } 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 20aae10ecde..b258f24d26f 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 @@ -34,6 +34,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -92,6 +94,9 @@ public class FileService { private static final String ARABIC_DEFAULT_VALUE = "1"; private static final String ROMAN_DEFAULT_VALUE = "I"; private static final String UNCOUNTED_DEFAULT_VALUE = " - "; + private static final String TEMP_EXTENSION = ".tmp"; + + private static final String SLASH = "/"; /** @@ -1432,9 +1437,8 @@ public URI deleteFirstSlashFromPath(URI uri) { * @param process Process * @param generatorSource Folder * @return whether given URI points to empty directory or not - * @throws IOException thrown if listing contents of given URI is not possible */ - public static boolean hasImages(Process process, Folder generatorSource) throws IOException, DAOException { + public static boolean hasImages(Process process, Folder generatorSource) { if (Objects.nonNull(generatorSource)) { Subfolder sourceFolder = new Subfolder(process, generatorSource); return !sourceFolder.listContents().isEmpty(); @@ -1450,4 +1454,114 @@ public static boolean hasImages(Process process, Folder generatorSource) throws public MetadataImageComparator getMetadataImageComparator() { return metadataImageComparator; } + + /** + * Rename media files of current process according to their corresponding media units order attribute. Given Map + * "filenameMapping" is altered via side effect and does not need to be returned. Instead, the number of acutally + * changed filenames is returned to the calling method. + * + * @param process Process object for which media files are renamed. + * @param workpiece Workpiece object of process + * @param filenameMapping Bidirectional map containing current filename mapping; empty until first renaming + * @return number of renamed media files + * @throws IOException when renaming files fails + * @throws URISyntaxException when creating URI for new filenames fails + */ + public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMap filenameMapping) + throws IOException, URISyntaxException { + int filenameLength = process.getProject().getFilenameLength(); + URI processDataUri = ServiceManager.getProcessService().getProcessDataDirectory(process); + + if (!processDataUri.toString().endsWith(SLASH)) { + processDataUri = URI.create(processDataUri + SLASH); + } + + int numberOfRenamedMedia = 0; + + // first, rename all files to new filenames plus "tmp" extension to avoid filename collisions + for (PhysicalDivision page : workpiece.getAllPhysicalDivisionChildrenFilteredByTypes(PhysicalDivision.TYPES)) { + String newFilename = StringUtils.leftPad(String.valueOf(page.getOrder()), filenameLength, '0'); + for (Entry variantURIEntry : page.getMediaFiles().entrySet()) { + URI fileUri = processDataUri.resolve(variantURIEntry.getValue()); + String newFilepath = newFilename + "." + FilenameUtils.getExtension(fileUri.getPath()) + TEMP_EXTENSION; + // skip files that already have the correct target name + if (!newFilename.equals(FilenameUtils.getBaseName(variantURIEntry.getValue().toString()))) { + URI tmpUri = fileManagementModule.rename(fileUri, processDataUri + newFilepath); + if (filenameMapping.containsValue(fileUri)) { + // update existing mapping of files that are renamed multiple times + filenameMapping.replace(filenameMapping.getKey(fileUri), tmpUri); + } else { + // add new mapping otherwise + filenameMapping.put(fileUri, tmpUri); + } + URI targetUri = new URI(StringUtils.removeStart(StringUtils.removeEnd(tmpUri.toString(), + TEMP_EXTENSION), process.getId() + SLASH)); + page.getMediaFiles().put(variantURIEntry.getKey(), targetUri); + numberOfRenamedMedia++; + } + } + } + + // then remove "tmp" extension from all filenames + for (Entry renamingEntry : filenameMapping.entrySet()) { + URI tempFilename = renamingEntry.getValue(); + String tempFilenameString = tempFilename.toString(); + // skip filename mappings from last renaming round that have not been renamed again + if (tempFilenameString.endsWith(TEMP_EXTENSION)) { + String newFilepath = StringUtils.removeEnd(tempFilename.toString(), TEMP_EXTENSION); + filenameMapping.put(renamingEntry.getKey(), fileManagementModule.rename(tempFilename, newFilepath)); + } + } + return numberOfRenamedMedia; + } + + /** + * 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 + * keys. + * + * @param filenameMappings Bidirectional map containing original filenames as keys and new filenames as values. + * @param workpiece Workpiece of current process + */ + public void revertRenaming(BidiMap filenameMappings, Workpiece workpiece) { + // revert media variant URIs for all media files in workpiece to previous, original values + for (PhysicalDivision physicalDivision : workpiece + .getAllPhysicalDivisionChildrenFilteredByTypes(PhysicalDivision.TYPES)) { + for (Entry mediaVariantURIEntry : physicalDivision.getMediaFiles().entrySet()) { + physicalDivision.getMediaFiles().put(mediaVariantURIEntry.getKey(), + filenameMappings.get(mediaVariantURIEntry.getValue())); + } + } + // revert filenames of media files to previous, original values + try { + List tempUris = new LinkedList<>(); + for (Entry mapping : filenameMappings.entrySet()) { + tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString() + + TEMP_EXTENSION)); + } + for (URI tempUri : tempUris) { + fileManagementModule.rename(tempUri, StringUtils.removeEnd(tempUri.toString(), TEMP_EXTENSION)); + } + } catch (IOException e) { + logger.error(e); + } + } + + /** + * Remove given map entry with whose value URI ends with given URI "unsavedMediaUri" from map + * and return updated map. + * @param unsavedMediaUri URI for which corresponding map entry is removed + * @param mappingMap bidirectional map from URIs to URIs + * @return updated bidirectional map + */ + public DualHashBidiMap removeUnsavedUploadMediaUriFromFileMapping(URI unsavedMediaUri, + DualHashBidiMap mappingMap) { + DualHashBidiMap updatedMap = new DualHashBidiMap<>(); + for (Map.Entry mapEntry : mappingMap.entrySet()) { + if (!mapEntry.getValue().toString().endsWith(unsavedMediaUri.toString())) { + updatedMap.put(mapEntry.getKey(), mapEntry.getValue()); + } + } + return updatedMap; + } } 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 09af57e37ce..963344c9b9d 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 @@ -1068,4 +1068,13 @@ public boolean hasAuthorityToDeleteMedia() { public boolean hasAuthorityToRunKitodoScripts() { return hasAnyAuthorityForClient("runKitodoScript"); } + + /** + * Check if the current user has the permission to rename media files. + * + * @return true if the current user has the permission to rename media files. + */ + public boolean hasAuthorityToRenameMediaFiles() { + return hasAnyAuthorityForClient("renameMedia"); + } } diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 23abe9e57bc..0c8a547360a 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -323,6 +323,10 @@ 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. dataEditor.structure.customizeDisplay=Anzeige anpassen dataEditor.structureTree.collapseAll=Strukturbaum komplett einklappen dataEditor.structureTree.expandAll=Strukturbaum komplett ausklappen @@ -449,6 +453,7 @@ fileGroupDelete=Dateigruppe l\u00F6schen fileMassImport=Datei Massenimport filename=Dateiname files=Dateien +filenameLengthForRenaming=Dateinamenl\u00e4nge f\u00FCr Umbenennung von Medien filter=Filter filterAdjust=Filter anpassen filterApply=Filter anwenden @@ -1271,6 +1276,7 @@ performTask=Aufgabe durchf\u00FChren assignTask=Aufgabe zuweisen overrideTask=Aufgabe \u00FCbernehmen superviseTask=Aufgabe beobachten +renameMedia=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 c8f8e4b61f1..7118d47bfd4 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -323,6 +323,10 @@ 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. dataEditor.structure.customizeDisplay=Customize display dataEditor.structureTree.collapseAll=Collapse all dataEditor.structureTree.expandAll=Expand all @@ -450,6 +454,7 @@ fileGroupDelete=Delete file group fileMassImport=File mass import filename=File name files=Files +filenameLengthForRenaming=Filename length for renaming media files filter=Filter filterAdjust=Adjust filter filterApply=Apply filter @@ -1273,6 +1278,7 @@ assignTask=Assign task overrideTask=Take on task superviseTask=Watch task resetWorkflow=Reset workflow +renameMedia=Rename media runKitodoScript=Execute KitodoScript viewAllAuthorities=View all authorities diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css index 0c12d7e24e7..a9ce8a3da3a 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -2358,6 +2358,11 @@ form#metadata div label, margin-bottom: var(--default-full-size); } +.ui-dialog .ui-panelgrid-cell div.ui-fileupload-content { + max-height: 120px; + overflow-y: scroll; +} + .ui-dialog .ui-panelgrid-cell div.ui-fileupload-content, .ui-dialog .ui-panelgrid-cell div.ui-fileupload-files { color: var(--blue); diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js index f9eb035e813..476ad6b0009 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js @@ -1010,6 +1010,9 @@ function activateButtons() { PF('save').enable(); PF('validate').enable(); PF('close').enable(); + if ($('#buttonForm\\:renameMedia').length > 0) { + PF('renameMedia').enable(); + } } function deactivateButtons() { @@ -1017,6 +1020,9 @@ function deactivateButtons() { PF('save').disable(); PF('validate').disable(); PF('close').disable(); + if ($('#buttonForm\\:renameMedia').length > 0) { + PF('renameMedia').disable(); + } } $(function () { diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/renamingMediaResults.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/renamingMediaResults.xhtml new file mode 100644 index 00000000000..2bc150e2283 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/renamingMediaResults.xhtml @@ -0,0 +1,95 @@ + + + + + + + + + +

#{msgs['dataEditor.renamingMediaComplete']}

+ +
+ +
+
+ + + + + +
+
+
+ +
+ + + + + + + +

#{msgs['dataEditor.renamingMediaError']}

+ +
+ +
+
+ + + + + +
+
+
+ +
+ +
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 fbed390910f..2d14e19728c 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 @@ -67,6 +67,17 @@ icon="fa fa-check fa-lg" iconPos="right" styleClass="secondary"/> +
- - + + + - +
@@ -80,5 +85,14 @@ + +
+ + + + +
+
diff --git a/Kitodo/src/main/webapp/pages/metadataEditor.xhtml b/Kitodo/src/main/webapp/pages/metadataEditor.xhtml index 79b1bc71772..9caf467410b 100644 --- a/Kitodo/src/main/webapp/pages/metadataEditor.xhtml +++ b/Kitodo/src/main/webapp/pages/metadataEditor.xhtml @@ -250,6 +250,7 @@ + diff --git a/Kitodo/src/test/java/org/kitodo/MockDatabase.java b/Kitodo/src/test/java/org/kitodo/MockDatabase.java index d4ebf69ca5e..0451516197a 100644 --- a/Kitodo/src/test/java/org/kitodo/MockDatabase.java +++ b/Kitodo/src/test/java/org/kitodo/MockDatabase.java @@ -124,6 +124,7 @@ public class MockDatabase { private static final int CUSTOM_CONFIGURATION_ID = 4; 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 { @@ -430,6 +431,10 @@ public static void insertAuthorities() throws DAOException { // Database statistics authorities.add(new Authority("viewDatabaseStatistic" + GLOBAL_ASSIGNABLE)); + // Rename media files + authorities.add(new Authority("renameMedia" + GLOBAL_ASSIGNABLE)); + authorities.add(new Authority("renameMedia" + CLIENT_ASSIGNABLE)); + for (Authority authority : authorities) { ServiceManager.getAuthorityService().saveToDatabase(authority); } @@ -914,7 +919,7 @@ private static void insertProjects() throws Exception { User secondUser = ServiceManager.getUserService().getById(2); User sixthUser = ServiceManager.getUserService().getById(6); - Client client = ServiceManager.getClientService().getById(1); + Client firstClient = ServiceManager.getClientService().getById(1); Project firstProject = new Project(); firstProject.setTitle("First project"); @@ -927,7 +932,7 @@ private static void insertProjects() throws Exception { firstProject.setMetsRightsOwner("Test Owner"); firstProject.getUsers().add(firstUser); firstProject.getUsers().add(secondUser); - firstProject.setClient(client); + firstProject.setClient(firstClient); ServiceManager.getProjectService().save(firstProject); Project secondProject = new Project(); @@ -938,9 +943,10 @@ private static void insertProjects() throws Exception { secondProject.setEndDate(Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); secondProject.setNumberOfPages(80); secondProject.setNumberOfVolumes(4); + secondProject.setFilenameLength(4); secondProject.getUsers().add(firstUser); secondProject.getUsers().add(sixthUser); - secondProject.setClient(client); + secondProject.setClient(firstClient); ServiceManager.getProjectService().save(secondProject); firstUser.getProjects().add(firstProject); @@ -1095,6 +1101,16 @@ public static int insertTestProcessForMetadataLockTestIntoSecondProject() throws return insertTestProcessIntoSecondProject(METADATA_LOCK_TEST_PROCESS_TITLE); } + /** + * Add test process for renaming media files. + * @return ID of created test process + * @throws DAOException when retrieving project fails + * @throws DataException when saving test process fails + */ + public static int insertTestProcessForRenamingMediaTestIntoSecondProject() throws DAOException, DataException { + return insertTestProcessIntoSecondProject(MEDIA_RENAMING_TEST_PROCESS_TITLE); + } + /** * Insert test process for media reference updates into database. * @return database ID of created test process @@ -1382,6 +1398,7 @@ private static void insertUsers() throws DAOException { Role withoutAuthoritiesRole = ServiceManager.getRoleService().getById(5); Role metadataRole = ServiceManager.getRoleService().getById(6); Role databaseRole = ServiceManager.getRoleService().getById(7); + Role renameMediaRole = ServiceManager.getRoleService().getById(8); User firstUser = new User(); firstUser.setName("Jan"); @@ -1396,6 +1413,7 @@ private static void insertUsers() throws DAOException { firstUser.getRoles().add(adminRole); firstUser.getRoles().add(generalRole); firstUser.getRoles().add(databaseRole); + firstUser.getRoles().add(renameMediaRole); firstUser.getClients().add(firstClient); ServiceManager.getUserService().saveToDatabase(firstUser); @@ -1543,6 +1561,15 @@ private static void insertRoles() throws DAOException { databaseStatisticsRole.setAuthorities(databaseStatisticAuthorities); ServiceManager.getRoleService().saveToDatabase(databaseStatisticsRole); + + // insert media renaming role + Role renameMediaRole = new Role(); + renameMediaRole.setTitle("Rename process media files"); + renameMediaRole.setClient(client); + renameMediaRole.setAuthorities(Collections.singletonList(ServiceManager.getAuthorityService().getByTitle("renameMedia" + GLOBAL_ASSIGNABLE))); + renameMediaRole.setAuthorities(Collections.singletonList(ServiceManager.getAuthorityService().getByTitle("renameMedia" + CLIENT_ASSIGNABLE))); + + ServiceManager.getRoleService().saveToDatabase(renameMediaRole); } private static void insertUserFilters() throws DAOException, DataException { diff --git a/Kitodo/src/test/java/org/kitodo/production/forms/ClientFormIT.java b/Kitodo/src/test/java/org/kitodo/production/forms/ClientFormIT.java index c2c9cfcc51e..0d5e40c13c9 100644 --- a/Kitodo/src/test/java/org/kitodo/production/forms/ClientFormIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/forms/ClientFormIT.java @@ -52,7 +52,7 @@ public void testRoleAdding() throws DAOException { final int numberOfAuthoritiesToCopy = ServiceManager.getRoleService().getAllRolesByClientId(2).get(0).getAuthorities() .size(); - Assert.assertEquals("Number of roles is incorrect", 7, numberOfRolesForFirstClient); + Assert.assertEquals("Number of roles is incorrect", 8, numberOfRolesForFirstClient); clientForm.getRolesForClient(); clientForm.setClientToCopyRoles(ServiceManager.getClientService().getById(2)); @@ -62,9 +62,9 @@ public void testRoleAdding() throws DAOException { numberOfRolesForFirstClient = ServiceManager.getRoleService().getAllRolesByClientId(1).size(); int numberOfOldAuthorities = ServiceManager.getRoleService().getAllRolesByClientId(2).get(0).getAuthorities() .size(); - int numberOfNewAuthorities = ServiceManager.getRoleService().getAllRolesByClientId(1).get(7).getAuthorities() + int numberOfNewAuthorities = ServiceManager.getRoleService().getAllRolesByClientId(1).get(8).getAuthorities() .size(); - Assert.assertEquals("Role was not added", 8, numberOfRolesForFirstClient); + Assert.assertEquals("Role was not added", 9, numberOfRolesForFirstClient); Assert.assertEquals("Authorities were not added", numberOfOldAuthorities, numberOfNewAuthorities); Assert.assertEquals("Authorities were removed from second client", numberOfAuthoritiesToCopy, numberOfOldAuthorities); diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java index d10fc12bc6b..12e676deaee 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java @@ -28,7 +28,7 @@ public class AuthorityServiceIT { private static final AuthorityService authorityService = ServiceManager.getAuthorityService(); - private static final int EXPECTED_AUTHORITIES_COUNT = 97; + private static final int EXPECTED_AUTHORITIES_COUNT = 99; @BeforeClass public static void prepareDatabase() throws Exception { @@ -82,6 +82,6 @@ public void shouldNotSaveAlreadyExistingAuthorities() throws Exception { @Test public void shouldGetAllClientAssignableAuthorities() throws DAOException { List authorities = authorityService.getAllAssignableToClients(); - assertEquals("Client assignable authorities were not found database!", 71, authorities.size()); + assertEquals("Client assignable authorities were not found database!", 72, authorities.size()); } } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/RoleServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/RoleServiceIT.java index c8983dc7310..654f8adbf13 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/RoleServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/RoleServiceIT.java @@ -35,7 +35,7 @@ public class RoleServiceIT { private static final RoleService roleService = ServiceManager.getRoleService(); - private static final int EXPECTED_ROLES_COUNT = 7; + private static final int EXPECTED_ROLES_COUNT = 8; private static final String WRONG_NUMBER_OF_ROLES = "Amount of roles assigned to client is incorrect!"; @@ -78,7 +78,7 @@ public void shouldGetAllRoles() throws Exception { @Test public void shouldGetAllRolesInGivenRange() throws Exception { List roles = roleService.getAll(1, 10); - assertEquals("Not all user's roles were found in database!", 6, roles.size()); + assertEquals("Not all user's roles were found in database!", 7, roles.size()); } @Test @@ -171,7 +171,7 @@ public void shouldSaveAndRemoveAuthorizationForRole() throws Exception { @Test public void shouldGetAllRolesByClientIds() { List roles = roleService.getAllRolesByClientId(1); - assertEquals(WRONG_NUMBER_OF_ROLES, 6, roles.size()); + assertEquals(WRONG_NUMBER_OF_ROLES, 7, roles.size()); roles = roleService.getAllRolesByClientId(2); assertEquals(WRONG_NUMBER_OF_ROLES, 1, roles.size()); @@ -185,6 +185,6 @@ public void shouldGetAllAvailableForAssignToUser() throws Exception { user = ServiceManager.getUserService().getById(2); roles = roleService.getAllAvailableForAssignToUser(user); - assertEquals(WRONG_NUMBER_OF_ROLES, 5, roles.size()); + assertEquals(WRONG_NUMBER_OF_ROLES, 6, roles.size()); } } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/security/SecurityAccessServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/security/SecurityAccessServiceIT.java index f3f4a4a3621..2b6ffe52a59 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/security/SecurityAccessServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/security/SecurityAccessServiceIT.java @@ -51,7 +51,7 @@ public void shouldGetAuthorities() throws DAOException { SecurityTestUtils.addUserDataToSecurityContext(user, 1); Collection authorities = SecurityContextHolder.getContext().getAuthentication() .getAuthorities(); - Assert.assertEquals("Security context holder does not hold the corresponding authorities", 168, + Assert.assertEquals("Security context holder does not hold the corresponding authorities", 171, authorities.size()); } diff --git a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java index 026ecf07842..4b93464daf3 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java @@ -51,9 +51,11 @@ 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"; private static int mediaReferencesProcessId = -1; private static int metadataLockProcessId = -1; private static int parentProcessId = -1; + private static int renamingMediaProcessId = -1; private static List dummyProcessIds = new LinkedList<>(); private static final String PARENT_PROCESS_TITLE = "Parent process"; @@ -65,6 +67,8 @@ public class MetadataST extends BaseTestSelenium { 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(); @@ -85,6 +89,12 @@ private static void prepareProcessHierarchyProcesses() throws DAOException, IOEx updateChildProcessIdsInParentProcessMetadataFile(); } + private static void prepareMediaRenamingProcess() throws DAOException, DataException, IOException { + insertDummyProcesses(); + insertTestProcessForRenamingMediaFiles(); + copyTestFilesForRenamingMediaFiles(); + } + /** * Prepare tests by inserting dummy processes into database and index for sub-folders of test metadata resources. * @throws DAOException when saving of dummy or test processes fails. @@ -97,6 +107,7 @@ public static void prepare() throws DAOException, DataException, IOException { prepareMetadataLockProcess(); prepareMediaReferenceProcess(); prepareProcessHierarchyProcesses(); + prepareMediaRenamingProcess(); } /** @@ -142,14 +153,14 @@ public void changeProcessLinkOrderTest() throws Exception { Assert.assertTrue("Wrong initial order of linked child processes", Pages.getMetadataEditorPage().getNameOfFirstLinkedChildProcess().endsWith(FIRST_CHILD_PROCESS_TITLE)); Assert.assertTrue("Wrong initial order of linked child processes", - Pages.getMetadataEditorPage().getNameOfSecondLinkedChildProcess().endsWith(SECOND_CHILD_PROCESS_TITLE)); + Pages.getMetadataEditorPage().getSecondRootElementChildLabel().endsWith(SECOND_CHILD_PROCESS_TITLE)); Pages.getMetadataEditorPage().changeOrderOfLinkedChildProcesses(); Pages.getMetadataEditorPage().saveAndExit(); Pages.getProcessesPage().goTo().editParentProcessMetadata(); Assert.assertTrue("Wrong resulting order of linked child processes", Pages.getMetadataEditorPage().getNameOfFirstLinkedChildProcess().endsWith(SECOND_CHILD_PROCESS_TITLE)); Assert.assertTrue("Wrong resulting order of linked child processes", - Pages.getMetadataEditorPage().getNameOfSecondLinkedChildProcess().endsWith(FIRST_CHILD_PROCESS_TITLE)); + Pages.getMetadataEditorPage().getSecondRootElementChildLabel().endsWith(FIRST_CHILD_PROCESS_TITLE)); } /** @@ -210,6 +221,25 @@ public void updateMediaReferencesTest() throws Exception { Pages.getMetadataEditorPage().acknowledgeFileReferenceChanges(); } + /** + * Verifies that renaming media files shows 'renaming successful' dialog and renames files correctly according to + * their corresponding physical divisions ORDER attribute. + * @throws Exception when page navigation fails + */ + @Test + public void renameMediaFilesTest() throws Exception { + login("kowal"); + Pages.getProcessesPage().goTo().editMetadata(MockDatabase.MEDIA_RENAMING_TEST_PROCESS_TITLE); + assertEquals("Second child node in structure tree has wrong label BEFORE renaming media files", + FIRST_STRUCTURE_TREE_NODE_LABEL, Pages.getMetadataEditorPage().getSecondRootElementChildLabel()); + Pages.getMetadataEditorPage().renameMedia(); + assertTrue("'Renaming media files was successful' dialog is not visible", Pages.getMetadataEditorPage() + .isRenamingMediaFilesDialogVisible()); + Pages.getMetadataEditorPage().acknowledgeRenamingMediaFiles(); + assertEquals("Second child node in structure tree has wrong label AFTER renaming media files", + SECOND_STRUCTURE_TREE_NODE_LABEL, Pages.getMetadataEditorPage().getSecondRootElementChildLabel()); + } + /** * Close metadata editor and logout after every test. * @throws Exception when page navigation fails @@ -238,6 +268,7 @@ public static void cleanup() throws DAOException, CustomResponseException, DataE } ProcessService.deleteProcess(mediaReferencesProcessId); ProcessService.deleteProcess(metadataLockProcessId); + ProcessService.deleteProcess(renamingMediaProcessId); } private void login(String username) throws InstantiationException, IllegalAccessException, InterruptedException { @@ -253,6 +284,10 @@ private static void insertTestProcessForMetadataLockTest() throws DAOException, metadataLockProcessId = MockDatabase.insertTestProcessForMetadataLockTestIntoSecondProject(); } + private static void insertTestProcessForRenamingMediaFiles() throws DAOException, DataException { + renamingMediaProcessId = MockDatabase.insertTestProcessForRenamingMediaTestIntoSecondProject(); + } + /** * Creates dummy parent process and links first two test processes as child processes to parent process. * @throws DAOException if loading of processes fails @@ -298,13 +333,21 @@ private static boolean processDirExists(int processId) { } private static void copyTestFilesForMediaReferences() throws IOException { + 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(mediaReferencesProcessId, TEST_MEDIA_REFERENCES_FILE); - URI processDir = Paths.get(ConfigCore.getKitodoDataDirectory(), String.valueOf(mediaReferencesProcessId)) + 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(), mediaReferencesProcessId + URI targetImages = Paths.get(ConfigCore.getKitodoDataDirectory(), processId + "/images/").toUri(); try { if (!ServiceManager.getFileService().isDirectory(targetImages)) { diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java index 1a64363a039..9603275db64 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/MetadataEditorPage.java @@ -54,6 +54,9 @@ public class MetadataEditorPage extends Page { @FindBy(id = "buttonForm:saveExit") private WebElement saveAndExitButton; + @FindBy(id = "buttonForm:renameMedia") + private WebElement renameMediaButton; + @FindBy(id = "logicalTree:0") private WebElement logicalTree; @@ -61,7 +64,7 @@ public class MetadataEditorPage extends Page { private WebElement firstChildProcess; @FindBy(id = "logicalTree:0_1") - private WebElement secondChildProcess; + private WebElement secondChildNode; @FindBy(id = "expandAllButton") private WebElement expandAllButton; @@ -69,6 +72,12 @@ public class MetadataEditorPage extends Page { @FindBy(id = "collapseAllButton") private WebElement collapseAllButton; + @FindBy(id = "renamingMediaSuccessDialog") + private WebElement renamingMediaSuccessDialog; + + @FindBy(id = "renamingMediaResultForm:okSuccess") + private WebElement okButtonRenameMediaFiles; + public MetadataEditorPage() { super("metadataEditor.jsf"); } @@ -121,6 +130,21 @@ public void acknowledgeFileReferenceChanges() { okButton.click(); } + /** + * Check and return whether information dialog about renamed media files is displayed or not. + * @return whether information dialog about renamed media files is displayed or not + */ + public boolean isRenamingMediaFilesDialogVisible() { + return renamingMediaSuccessDialog.isDisplayed(); + } + + /** + * Acknowledge media files being successfully renamed dialog by clicking "OK" button on corresponding popup dialog. + */ + public void acknowledgeRenamingMediaFiles() { + okButtonRenameMediaFiles.click(); + } + /** * Close Metadata editor to release metadata lock. */ @@ -135,16 +159,25 @@ public void save() { saveButton.click(); } + /** + * Click "rename media" button. + */ + public void renameMedia() { + renameMediaButton.click(); + await().ignoreExceptions().pollDelay(300, TimeUnit.MILLISECONDS).atMost(3, TimeUnit.SECONDS) + .until(this::isRenamingMediaFilesDialogVisible); + } + /** * Change order of child processes in metadata editor by moving the second child process before * the first child process via drag and drop. */ public void changeOrderOfLinkedChildProcesses() { - secondChildProcess.click(); + secondChildNode.click(); WebDriver webDriver = Browser.getDriver(); Actions moveAction = new Actions(webDriver); WebElement dropArea = logicalTree.findElement(By.className("ui-tree-droppoint")); - moveAction.dragAndDrop(secondChildProcess, dropArea).build().perform(); + moveAction.dragAndDrop(secondChildNode, dropArea).build().perform(); } public ProcessesPage saveAndExit() throws InstantiationException, IllegalAccessException { @@ -156,10 +189,13 @@ public String getNameOfFirstLinkedChildProcess() { return firstChildProcess.getText(); } - public String getNameOfSecondLinkedChildProcess() { - return secondChildProcess.getText(); + /** + * Get label of second child tree node of structure tree root node. + * @return label of second child tree node of structure tree root node + */ + public String getSecondRootElementChildLabel() { + return secondChildNode.getText(); } - /** * Click "expand all" button in structure panel of metadata editor. */ diff --git a/Kitodo/src/test/resources/metadata/testRenameMediaMeta.xml b/Kitodo/src/test/resources/metadata/testRenameMediaMeta.xml new file mode 100644 index 00000000000..28b98611bc1 --- /dev/null +++ b/Kitodo/src/test/resources/metadata/testRenameMediaMeta.xml @@ -0,0 +1,91 @@ + + + + + + + + + Test process for renaming media files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +