Skip to content

Commit

Permalink
Rename media files corresponding to their order value
Browse files Browse the repository at this point in the history
  • Loading branch information
solth committed Jul 21, 2023
1 parent c4befdf commit d5600f2
Show file tree
Hide file tree
Showing 22 changed files with 572 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -187,9 +188,20 @@ public List<LogicalDivision> getAllLogicalDivisions() {
* @return all physical divisions with type "page", sorted by their {@code order}
*/
public List<PhysicalDivision> 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<PhysicalDivision> getAllPhysicalDivisionChildrenFilteredByTypes(List<String> 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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ public class Project extends BaseIndexedBean implements Comparable<Project> {
@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.
*/
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--
-- (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
--
-- This file is part of the Kitodo project.
--
-- It is licensed under GNU General Public License version 3 or later.
--
-- For the full copyright and license information, please read the
-- GPL3-License.txt file that was distributed with this source code.
--

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;
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -202,6 +204,10 @@ public class DataEditorForm implements MetadataTreeTableInterface, RulesetSetupI
private MediaProvider mediaProvider;
private boolean mediaUpdated = false;

private DualHashBidiMap<URI, URI> filenameMapping = new DualHashBidiMap<>();

private String renamingError = "";

/**
* Public constructor.
*/
Expand Down Expand Up @@ -382,6 +388,7 @@ public String closeAndReturn() {
public void close() {
deleteNotSavedUploadedMedia();
unsavedDeletedMedia.clear();
ServiceManager.getFileService().revertRenaming(filenameMapping.inverseBidiMap(), workpiece);
metadataPanel.clear();
structurePanel.clear();
workpiece = null;
Expand Down Expand Up @@ -488,6 +495,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)) {
Expand Down Expand Up @@ -1100,4 +1109,44 @@ 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 {
filenameMapping = ServiceManager.getFileService().renameMediaFiles(process, workpiece);
} 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;
}

/**
* Get number of renamed images as size of filename mapping.
* @return number of renamed images
*/
public int getNumberOfRenamedImages() {
return filenameMapping.size();
}

private void showPanels() {
galleryPanel.show();
paginationPanel.show();
structurePanel.show();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = "/";


/**
Expand Down Expand Up @@ -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();
Expand All @@ -1450,4 +1454,80 @@ 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.
*
* @param process Process object for which media files are renamed.
* @param workpiece Workpiece object of process
* @return Map containing media files original filenames as keys and new filenames as values
* @throws IOException when renaming files fails
* @throws URISyntaxException when creating URI for new filenames fails
*/
public DualHashBidiMap<URI, URI> renameMediaFiles(Process process, Workpiece workpiece) throws IOException, URISyntaxException {
DualHashBidiMap<URI, URI> filenameMapping = new DualHashBidiMap<>();
int filenameLength = process.getProject().getFilenameLength();
URI processDataUri = ServiceManager.getProcessService().getProcessDataDirectory(process);

if (!processDataUri.toString().endsWith(SLASH)) {
processDataUri = URI.create(processDataUri + SLASH);
}

// 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<MediaVariant, URI> 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);
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);
}
}
}

// then remove "tmp" extension from all filenames
for (Entry<URI, URI> renamingEntry : filenameMapping.entrySet()) {
URI tempFilename = renamingEntry.getValue();
String newFilepath = StringUtils.removeEnd(tempFilename.toString(), TEMP_EXTENSION);
filenameMapping.put(renamingEntry.getKey(), fileManagementModule.rename(tempFilename, newFilepath));
}
return filenameMapping;
}

/**
* 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<URI, URI> 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<MediaVariant, URI> mediaVariantURIEntry : physicalDivision.getMediaFiles().entrySet()) {
physicalDivision.getMediaFiles().put(mediaVariantURIEntry.getKey(),
filenameMappings.get(mediaVariantURIEntry.getValue()));
}
}
// revert filenames of media files to previous, original values
try {
List<URI> tempUris = new LinkedList<>();
for (Entry<URI, URI> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
6 changes: 6 additions & 0 deletions Kitodo/src/main/resources/messages/messages_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=Mediendateien 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions Kitodo/src/main/resources/messages/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=media files 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1010,13 +1010,19 @@ function activateButtons() {
PF('save').enable();
PF('validate').enable();
PF('close').enable();
if ($('#buttonForm\\:renameMedia').length > 0) {
PF('renameMedia').enable();
}
}

function deactivateButtons() {
PF('saveExit').disable();
PF('save').disable();
PF('validate').disable();
PF('close').disable();
if ($('#buttonForm\\:renameMedia').length > 0) {
PF('renameMedia').disable();
}
}

$(function () {
Expand Down
Loading

0 comments on commit d5600f2

Please sign in to comment.