diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_107__Add_authorities_for_upload_and_delete_media.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_107__Add_authorities_for_upload_and_delete_media.sql new file mode 100644 index 00000000000..11c663451e3 --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_107__Add_authorities_for_upload_and_delete_media.sql @@ -0,0 +1,28 @@ +-- +-- (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. +-- + +-- Insert authorities for upload and delete media in metadata editor. + +INSERT IGNORE INTO authority (title) VALUES ('uploadMedia_globalAssignable'); +INSERT IGNORE INTO authority (title) VALUES ('uploadMedia_clientAssignable'); + +INSERT IGNORE INTO authority (title) VALUES ('deleteMedia_globalAssignable'); +INSERT IGNORE INTO authority (title) VALUES ('deleteMedia_clientAssignable'); + +INSERT IGNORE INTO role_x_authority (role_id, authority_id) +SELECT (SELECT id FROM role WHERE title = 'Administration'), id FROM authority WHERE title = 'uploadMedia_globalAssignable'; +INSERT IGNORE INTO role_x_authority (role_id, authority_id) +SELECT (SELECT id FROM role WHERE title = 'Administration'), id FROM authority WHERE title = 'uploadMedia_clientAssignable'; + +INSERT IGNORE INTO role_x_authority (role_id, authority_id) +SELECT (SELECT id FROM role WHERE title = 'Administration'), id FROM authority WHERE title = 'deleteMedia_globalAssignable'; +INSERT IGNORE INTO role_x_authority (role_id, authority_id) +SELECT (SELECT id FROM role WHERE title = 'Administration'), id FROM authority WHERE title = 'deleteMedia_clientAssignable'; diff --git a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java index 0a5fa812d5d..a4f23fe177a 100644 --- a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java +++ b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java @@ -313,6 +313,11 @@ public enum ParameterCore implements ParameterInterface { */ METS_EDITOR_DISPLAY_FILE_MANIPULATION(new Parameter("metsEditor.displayFileManipulation")), + /** + * Maximum number of media that can be uploaded at the same time in mets editor. + */ + METS_EDITOR_MAX_UPLOADED_MEDIA(new Parameter("metsEditor.maxUploadedMedia")), + /** * Comma-separated list of Strings which may be enclosed in double quotes. * Separators available for double page pagination modes. 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 a931a75dc4f..b37293721a6 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java @@ -431,6 +431,16 @@ public boolean hasAuthorityToUnassignTasks() { return securityAccessService.hasAuthorityToUnassignTasks(); } + + /** + * Check if the current user has the authority to upload media in metadataeditor. + * + * @return true if the current user has the authority to to upload media in metadataeditor + */ + public boolean hasAuthorityToUploadMedia() { + return securityAccessService.hasAuthorityToUploadMedia(); + } + /** * Check if the current user has the authority to edit the role. * 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 d2048938948..b2b2237fb0b 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.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; @@ -43,8 +44,10 @@ import org.kitodo.api.dataformat.Workpiece; import org.kitodo.api.validation.State; import org.kitodo.api.validation.ValidationResult; +import org.kitodo.config.ConfigCore; import org.kitodo.data.database.beans.DataEditorSetting; import org.kitodo.data.database.beans.Process; +import org.kitodo.data.database.beans.Project; import org.kitodo.data.database.beans.User; import org.kitodo.data.database.exceptions.DAOException; import org.kitodo.exceptions.InvalidImagesException; @@ -96,6 +99,7 @@ public class DataEditorForm implements RulesetSetupInterface, Serializable { */ private final EditPagesDialog editPagesDialog; + private final UploadFileDialog uploadFileDialog ; /** * Backing bean for the gallery panel. */ @@ -179,6 +183,10 @@ public class DataEditorForm implements RulesetSetupInterface, Serializable { private static final String DESKTOP_LINK = "/pages/desktop.jsf"; + private List unsavedUploadedMedia = new ArrayList<>(); + + private boolean folderConfigurationComplete = false; + /** * Public constructor. */ @@ -192,6 +200,7 @@ public DataEditorForm() { this.addPhysicalDivisionDialog = new AddPhysicalDivisionDialog(this); this.changeDocStrucTypeDialog = new ChangeDocStrucTypeDialog(this); this.editPagesDialog = new EditPagesDialog(this); + this.uploadFileDialog = new UploadFileDialog(this); acquisitionStage = "edit"; } @@ -226,17 +235,8 @@ public String open(String processID, String referringView) { this.process = ServiceManager.getProcessService().getById(Integer.parseInt(processID)); this.currentChildren.addAll(process.getChildren()); this.user = ServiceManager.getUserService().getCurrentUser(); - - if (templateTaskId > 0) { - dataEditorSetting = ServiceManager.getDataEditorSettingService().loadDataEditorSetting(user.getId(), templateTaskId); - if (Objects.isNull(dataEditorSetting)) { - dataEditorSetting = new DataEditorSetting(); - dataEditorSetting.setUserId(user.getId()); - dataEditorSetting.setTaskId(templateTaskId); - } - } else { - dataEditorSetting = null; - } + this.checkProjectFolderConfiguration(); + this.loadDataEditorSettings(); User blockedUser = MetadataLock.getLockUser(process.getId()); if (Objects.nonNull(blockedUser) && !blockedUser.equals(this.user)) { @@ -253,6 +253,7 @@ public String open(String processID, String referringView) { return referringView; } selectedMedia = new LinkedList<>(); + unsavedUploadedMedia = new ArrayList<>(); init(); MetadataLock.setLocked(process.getId(), user); } catch (IOException | DAOException | InvalidImagesException | NoSuchElementException e) { @@ -262,6 +263,34 @@ public String open(String processID, String referringView) { return "/pages/metadataEditor?faces-redirect=true"; } + private void checkProjectFolderConfiguration() { + if (Objects.nonNull(this.process)) { + Project project = this.process.getProject(); + if (Objects.nonNull(project)) { + this.folderConfigurationComplete = Objects.nonNull(project.getGeneratorSource()) + && Objects.nonNull(project.getMediaView()) && Objects.nonNull(project.getPreview()); + } else { + this.folderConfigurationComplete = false; + } + } else { + this.folderConfigurationComplete = false; + } + } + + private void loadDataEditorSettings() { + if (templateTaskId > 0) { + dataEditorSetting = ServiceManager.getDataEditorSettingService().loadDataEditorSetting(user.getId(), + templateTaskId); + if (Objects.isNull(dataEditorSetting)) { + dataEditorSetting = new DataEditorSetting(); + dataEditorSetting.setUserId(user.getId()); + dataEditorSetting.setTaskId(templateTaskId); + } + } else { + dataEditorSetting = null; + } + } + /** * Opens the METS file. * @@ -308,6 +337,7 @@ private void init() { * @return the referring view, to return there */ public String close() { + deleteNotSavedUploadedMedia(); metadataPanel.clear(); structurePanel.clear(); workpiece = null; @@ -326,6 +356,21 @@ public String close() { } } + private void deleteNotSavedUploadedMedia() { + URI uri = Paths.get(ConfigCore.getKitodoDataDirectory(), + ServiceManager.getProcessService().getProcessDataDirectory(this.process).getPath()).toUri(); + for (PhysicalDivision mediaUnit : this.unsavedUploadedMedia) { + for (URI fileURI : mediaUnit.getMediaFiles().values()) { + try { + ServiceManager.getFileService().delete(uri.resolve(fileURI)); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + this.unsavedUploadedMedia.clear(); + } + /** * Validate the structure and metadata. * @@ -377,6 +422,7 @@ private String save(boolean close) { try (OutputStream out = ServiceManager.getFileService().write(mainFileUri)) { ServiceManager.getMetsService().save(workpiece, out); ServiceManager.getProcessService().saveToIndex(process,false); + unsavedUploadedMedia.clear(); if (close) { return close(); } else { @@ -437,6 +483,15 @@ public String getAcquisitionStage() { return acquisitionStage; } + /** + * Get unsavedUploadedMedia. + * + * @return value of unsavedUploadedMedia + */ + public List getUnsavedUploadedMedia() { + return unsavedUploadedMedia; + } + /** * Returns the backing bean for the add doc struc type dialog. This function * is used by PrimeFaces to access the elements of the add doc struc type @@ -848,4 +903,22 @@ public void saveDataEditorSetting() { templateTaskId); } } + + /** + * Get uploadFileDialog. + * + * @return value of uploadFileDialog + */ + public UploadFileDialog getUploadFileDialog() { + return uploadFileDialog; + } + + /** + * Get folderConfigurationComplete. + * + * @return value of folderConfigurationComplete + */ + public boolean isFolderConfigurationComplete() { + return folderConfigurationComplete; + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java new file mode 100644 index 00000000000..3b6baf280a4 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java @@ -0,0 +1,446 @@ +/* + * (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.forms.dataeditor; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.faces.model.SelectItem; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.api.dataformat.LogicalDivision; +import org.kitodo.api.dataformat.MediaVariant; +import org.kitodo.api.dataformat.PhysicalDivision; +import org.kitodo.api.dataformat.View; +import org.kitodo.config.ConfigCore; +import org.kitodo.config.enums.ParameterCore; +import org.kitodo.data.database.beans.Folder; +import org.kitodo.exceptions.InvalidImagesException; +import org.kitodo.exceptions.NoSuchMetadataFieldException; +import org.kitodo.production.enums.GenerationMode; +import org.kitodo.production.helper.Helper; +import org.kitodo.production.helper.VariableReplacer; +import org.kitodo.production.helper.tasks.EmptyTask; +import org.kitodo.production.helper.tasks.TaskManager; +import org.kitodo.production.helper.tasks.TaskState; +import org.kitodo.production.metadata.InsertionPosition; +import org.kitodo.production.metadata.MetadataEditor; +import org.kitodo.production.model.Subfolder; +import org.kitodo.production.services.ServiceManager; +import org.kitodo.production.services.file.SubfolderFactoryService; +import org.kitodo.production.services.image.ImageGenerator; +import org.kitodo.production.thread.TaskImageGeneratorThread; +import org.primefaces.PrimeFaces; +import org.primefaces.event.FileUploadEvent; +import org.primefaces.model.TreeNode; +import org.primefaces.model.UploadedFile; + +public class UploadFileDialog { + private static final Logger logger = LogManager.getLogger(UploadFileDialog.class); + + private final DataEditorForm dataEditor; + private UploadedFile file; + private String mimeType; + private String fileExtension; + private String use; + private URI sourceFolderURI; + private List possiblePositions = new ArrayList<>(); + private InsertionPosition selectedPosition; + private Folder sourceFolder; + private final List contentFolders = new ArrayList<>(); + private MediaVariant mediaVariant; + private int indexSelectedMedia; + private LogicalDivision parent; + private URI uploadFileUri; + private Subfolder generatorSource; + private final int fileLimit = ConfigCore.getIntParameter(ParameterCore.METS_EDITOR_MAX_UPLOADED_MEDIA); + private List> selectedMedia = new LinkedList<>(); + private Integer progress; + private List generateMediaTasks = new ArrayList<>(); + private final List taskBlockedStates = Arrays.asList(TaskState.CRASHED, TaskState.STOPPED, TaskState.STOPPING); + + /** + * Constructor. + * + * @param dataEditor Instance of DataEditorForm where this instance of UploadFileDialog was created. + */ + UploadFileDialog(DataEditorForm dataEditor) { + this.dataEditor = dataEditor; + } + + /** + * Get fileExtension. + * + * @return value of fileExtension + */ + public String getFileExtension() { + return fileExtension; + } + + /** + * Set fileExtension. + * + * @param fileExtension as java.lang.String + */ + public void setFileExtension(String fileExtension) { + this.fileExtension = fileExtension; + } + + /** + * Get fileLimit. + * + * @return value of fileLimit + */ + public int getFileLimit() { + return fileLimit; + } + + /** + * Get file. + * + * @return value of file + */ + public UploadedFile getFile() { + return file; + } + + /** + * Set file. + * + * @param file as org.primefaces.model.UploadedFile + */ + public void setFile(UploadedFile file) { + this.file = file; + } + + /** + * Get possiblePositions. + * + * @return value of possiblePositions + */ + public List getPossiblePositions() { + return possiblePositions; + } + + /** + * Set possiblePositions. + * + * @param possiblePositions as java.util.List of SelectItem + */ + public void setPossiblePositions(List possiblePositions) { + this.possiblePositions = possiblePositions; + } + + /** + * Get selectedPosition. + * + * @return value of selectedPosition + */ + public InsertionPosition getSelectedPosition() { + return selectedPosition; + } + + /** + * Set selectedPosition. + * + * @param selectedPosition as org.kitodo.production.metadata.InsertionPosition + */ + public void setSelectedPosition(InsertionPosition selectedPosition) { + this.selectedPosition = selectedPosition; + } + + /** + * Get progress. + * + * @return value of progress + */ + public int getProgress() throws NoSuchMetadataFieldException, InvalidImagesException { + if (generateMediaTasks.stream().anyMatch(emptyTask -> taskBlockedStates.contains(emptyTask.getTaskState()))) { + PrimeFaces.current().executeScript("PF('progressBar').cancel();"); + updateWorkpiece(); + } else { + progress = updateProgress(); + } + return progress; + } + + /** + * Set progress. + * + * @param progress as int + */ + public void setProgress(Integer progress) { + this.progress = progress; + } + + private Integer updateProgress() { + if (!generateMediaTasks.isEmpty() && progress != 100) { + return generateMediaTasks.stream().mapToInt(EmptyTask::getProgress).sum() / generateMediaTasks.size(); + } else { + return progress; + } + } + + private MediaVariant getMediaVariant() { + MediaVariant mediaVariant = new MediaVariant(); + mediaVariant.setMimeType(mimeType); + mediaVariant.setUse(use); + return mediaVariant; + } + + private String getPhysicalDivType() { + if (mimeType.contains("image")) { + return PhysicalDivision.TYPE_PAGE; + } + if (mimeType.contains("audio")) { + return PhysicalDivision.TYPE_TRACK; + } + return PhysicalDivision.TYPE_OTHER; + } + + private boolean setUpFolders() { + VariableReplacer variableReplacer = new VariableReplacer(null, dataEditor.getProcess(), null); + sourceFolder = dataEditor.getProcess().getProject().getGeneratorSource(); + Folder mediaView = dataEditor.getProcess().getProject().getMediaView(); + Folder preview = dataEditor.getProcess().getProject().getPreview(); + + if (Objects.isNull(sourceFolder) || Objects.isNull(mediaView) || Objects.isNull(preview)) { + return false; + } + + sourceFolder.setPath(variableReplacer.replace(sourceFolder.getRelativePath())); + mediaView.setPath(variableReplacer.replace(mediaView.getRelativePath())); + preview.setPath(variableReplacer.replace(preview.getRelativePath())); + + if (folderExists(sourceFolder) && folderExists(mediaView) && folderExists(preview)) { + sourceFolderURI = getFolderURI(sourceFolder); + contentFolders.add(mediaView); + contentFolders.add(preview); + return true; + } + return false; + } + + private void sortViews(List views) { + views.sort(Comparator.comparing(v -> FilenameUtils.getBaseName( + v.getPhysicalDivision().getMediaFiles().entrySet().iterator().next().getValue().getPath()))); + } + + private boolean folderExists(Folder folder) { + URI folderURI = getFolderURI(folder); + if (!ServiceManager.getFileService().fileExist(folderURI)) { + Helper.setErrorMessage("errorDirectoryNotFound", new Object[]{folderURI}); + return false; + } + return true; + } + + private URI getFolderURI(Folder folder) { + return Paths.get(ConfigCore.getKitodoDataDirectory(), + ServiceManager.getProcessService().getProcessDataDirectory(dataEditor.getProcess()).getPath(), + folder.getRelativePath()).toUri(); + } + + private void initPosition() { + TreeNode selectedLogicalNode = dataEditor.getStructurePanel().getSelectedLogicalNode(); + if (Objects.nonNull(selectedLogicalNode) + && selectedLogicalNode.getData() instanceof StructureTreeNode) { + StructureTreeNode structureTreeNode = (StructureTreeNode) selectedLogicalNode.getData(); + if (structureTreeNode.getDataObject() instanceof View) { + if (Objects.nonNull(selectedLogicalNode.getParent()) + && selectedLogicalNode.getParent().getData() instanceof StructureTreeNode + && Objects.nonNull(((StructureTreeNode) selectedLogicalNode.getParent().getData()) + .getDataObject()) + && ((StructureTreeNode) selectedLogicalNode.getParent().getData()).getDataObject() + instanceof LogicalDivision) { + parent = + (LogicalDivision) ((StructureTreeNode) selectedLogicalNode.getParent().getData()) + .getDataObject(); + indexSelectedMedia = parent.getViews().indexOf((View)structureTreeNode.getDataObject()); + + } + } else if (structureTreeNode.getDataObject() instanceof LogicalDivision) { + parent = (LogicalDivision) structureTreeNode.getDataObject(); + } + } + } + + private void preparePossiblePositions() { + possiblePositions = new ArrayList<>(); + TreeNode selectedLogicalNode = dataEditor.getStructurePanel().getSelectedLogicalNode(); + if (Objects.nonNull(selectedLogicalNode) + && selectedLogicalNode.getData() instanceof StructureTreeNode) { + StructureTreeNode structureTreeNode = (StructureTreeNode) selectedLogicalNode.getData(); + if (structureTreeNode.getDataObject() instanceof View) { + possiblePositions.add(new SelectItem(InsertionPosition.BEFORE_CURRENT_ELEMENT, + Helper.getTranslation("dataEditor.position.beforeCurrentElement"))); + possiblePositions.add(new SelectItem(InsertionPosition.AFTER_CURRENT_ELEMENT, + Helper.getTranslation("dataEditor.position.afterCurrentElement"))); + } else { + possiblePositions.add(new SelectItem(InsertionPosition.FIRST_CHILD_OF_CURRENT_ELEMENT, + Helper.getTranslation("dataEditor.position.asFirstChildOfCurrentElement"))); + possiblePositions.add(new SelectItem(InsertionPosition.LAST_CHILD_OF_CURRENT_ELEMENT, + Helper.getTranslation("dataEditor.position.asLastChildOfCurrentElement"))); + } + } + } + + /** + * Prepare popup dialog. + */ + public void prepare() { + progress = 0; + generateMediaTasks = new ArrayList<>(); + selectedMedia = new LinkedList<>(); + if (!setUpFolders()) { return; } + generatorSource = new Subfolder(dataEditor.getProcess(), sourceFolder); + mimeType = sourceFolder.getMimeType(); + use = sourceFolder.getFileGroup(); + mediaVariant = getMediaVariant(); + fileExtension = generatorSource.getFileFormat().getExtension(false); + preparePossiblePositions(); + initPosition(); + PrimeFaces.current().executeScript("PF('uploadFileDialog').show()"); + } + + /** + * Upload media. + * + * @param event as FileUploadEvent + */ + public void uploadMedia(FileUploadEvent event) { + if (event.getFile() != null) { + + PhysicalDivision physicalDivision = MetadataEditor.addPhysicalDivision(getPhysicalDivType(), + dataEditor.getWorkpiece(), dataEditor.getWorkpiece().getPhysicalStructure(), + InsertionPosition.LAST_CHILD_OF_CURRENT_ELEMENT); + uploadFileUri = sourceFolderURI.resolve(event.getFile().getFileName()); + + //TODO: Find a better way to avoid overwriting an existing file + if (ServiceManager.getFileService().fileExist(uploadFileUri)) { + String newFileName = ServiceManager.getFileService().getFileName(uploadFileUri) + + "_" + Helper.generateRandomString(3) + "." + fileExtension; + uploadFileUri = sourceFolderURI.resolve(newFileName); + } + physicalDivision.getMediaFiles().put(mediaVariant, uploadFileUri); + //upload file in sourceFolder + try (OutputStream outputStream = ServiceManager.getFileService().write(uploadFileUri)) { + IOUtils.copy(event.getFile().getInputstream(), outputStream); + } catch (IOException e) { + Helper.setErrorMessage(e.getLocalizedMessage(), logger, e); + } + dataEditor.getUnsavedUploadedMedia().add(physicalDivision); + selectedMedia.add(new ImmutablePair<>(physicalDivision, parent)); + PrimeFaces.current().executeScript("PF('notifications').renderMessage({'summary':'" + + Helper.getTranslation("mediaUploaded", Collections.singletonList(event.getFile().getFileName())) + + "','severity':'info'});"); + } + } + + /** + * Generate newly uploaded media. + */ + public void generateNewUploadedMedia() { + List outputs = SubfolderFactoryService.createAll(dataEditor.getProcess(), contentFolders); + ImageGenerator imageGenerator; + if (generatorSource.listContents().isEmpty()) { + imageGenerator = new ImageGenerator(generatorSource, GenerationMode.ALL, outputs); + } else { + imageGenerator = new ImageGenerator(generatorSource, GenerationMode.MISSING, outputs); + } + EmptyTask emptyTask = new TaskImageGeneratorThread(dataEditor.getProcess().getTitle(), imageGenerator); + TaskManager.addTask(emptyTask); + generateMediaTasks.add(emptyTask); + } + + /** + * Reset the progress bar after generating media is completed and update the workpiece. + */ + public void updateWorkpiece() throws InvalidImagesException, NoSuchMetadataFieldException { + generateMediaTasks.clear(); + addMediaToWorkpiece(); + refresh(); + if (progress != 100) { + Helper.setErrorMessage("generateMediaFailed"); + PrimeFaces.current().executeScript("PF('uploadFileDialog').hide();"); + PrimeFaces.current().ajax().update("logicalTree", "metadataAccordion:logicalMetadataWrapperPanel", + "paginationForm:paginationWrapperPanel", "galleryWrapperPanel"); + } else { + Helper.setMessage(Helper.getTranslation("uploadMediaCompleted")); + } + progress = 0; + } + + private void addMediaToWorkpiece() throws InvalidImagesException { + ServiceManager.getFileService().searchForMedia(dataEditor.getProcess(), dataEditor.getWorkpiece()); + + List views = selectedMedia.stream() + .map(v -> MetadataEditor.createUnrestrictedViewOn(v.getKey())) + .collect(Collectors.toList()); + sortViews(views); + switch (selectedPosition) { + case FIRST_CHILD_OF_CURRENT_ELEMENT: + parent.getViews().addAll(0, views); + break; + case LAST_CHILD_OF_CURRENT_ELEMENT: + parent.getViews().addAll(views); + break; + case AFTER_CURRENT_ELEMENT: + parent.getViews().addAll(indexSelectedMedia + 1, views); + break; + case BEFORE_CURRENT_ELEMENT: + parent.getViews().addAll(indexSelectedMedia, views); + break; + default: + throw new IllegalArgumentException("Position of new div element is not supported"); + } + } + + /** + * Refresh the metadataeditor after uploading media. + */ + public void refresh() throws NoSuchMetadataFieldException { + if (uploadFileUri != null && ServiceManager.getFileService().fileExist(uploadFileUri)) { + selectedMedia.sort((Comparator.comparing(v -> FilenameUtils.getBaseName( + v.getKey().getMediaFiles().entrySet().iterator().next().getValue().getPath())))); + dataEditor.getSelectedMedia().clear(); + dataEditor.getSelectedMedia().addAll(selectedMedia); + dataEditor.getStructurePanel().show(); + dataEditor.getStructurePanel().preserve(); + dataEditor.refreshStructurePanel(); + dataEditor.getGalleryPanel().show(); + dataEditor.getStructurePanel().updateLogicalNodeSelection( + dataEditor.getGalleryPanel().getGalleryMediaContent(selectedMedia.get(selectedMedia.size() - 1).getKey()), parent); + StructureTreeNode structureTreeNode = new StructureTreeNode(selectedMedia.get(selectedMedia.size() - 1).getKey().getLabel(), + false, false, + MetadataEditor.createUnrestrictedViewOn(selectedMedia.get(selectedMedia.size() - 1).getKey())); + dataEditor.switchStructure(structureTreeNode, false); + dataEditor.getPaginationPanel().show(); + uploadFileUri = null; + } + } +} diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/tasks/EmptyTask.java b/Kitodo/src/main/java/org/kitodo/production/helper/tasks/EmptyTask.java index 1453a957415..a1fbd70e803 100644 --- a/Kitodo/src/main/java/org/kitodo/production/helper/tasks/EmptyTask.java +++ b/Kitodo/src/main/java/org/kitodo/production/helper/tasks/EmptyTask.java @@ -15,7 +15,6 @@ import java.time.temporal.ChronoUnit; import java.util.Objects; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; @@ -278,7 +277,7 @@ public String getStateDescription() { * .PREPARE_FOR_RESTART— and is able to restart after cloning and replacing * it. *
{@code STOPPING}
- *
The thread has received a request to interrupt but didn’t stop + *
The thread has received a request to interrupt but did not stop * yet.
*
{@code WORKING}
*
The thread is in operation.
@@ -286,7 +285,7 @@ public String getStateDescription() { * * @return the task state */ - TaskState getTaskState() { + public TaskState getTaskState() { switch (getState()) { case NEW: return TaskState.NEW; diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/tasks/TaskState.java b/Kitodo/src/main/java/org/kitodo/production/helper/tasks/TaskState.java index 98071a65a72..cf0f779e075 100644 --- a/Kitodo/src/main/java/org/kitodo/production/helper/tasks/TaskState.java +++ b/Kitodo/src/main/java/org/kitodo/production/helper/tasks/TaskState.java @@ -37,6 +37,6 @@ *
The thread is in operation.
* */ -enum TaskState { +public enum TaskState { CRASHED, FINISHED, NEW, STOPPED, STOPPING, WORKING } 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 a694194be88..655cd8f0225 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 @@ -446,6 +446,16 @@ public boolean hasAuthorityToUnassignTasks() { return hasAuthorityGlobalOrForClient("unassignTasks"); } + + /** + * Check if the current user has the authority to upload media in metadataeditor. + * + * @return true if the current user has the authority to to upload media in metadataeditor + */ + public boolean hasAuthorityToUploadMedia() { + return hasAuthorityGlobalOrForClient("uploadMedia"); + } + /** * Checks if the current user has the authority to edit the role. * diff --git a/Kitodo/src/main/resources/kitodo_config.properties b/Kitodo/src/main/resources/kitodo_config.properties index fcb042a01f8..5f1a556f7c0 100644 --- a/Kitodo/src/main/resources/kitodo_config.properties +++ b/Kitodo/src/main/resources/kitodo_config.properties @@ -291,6 +291,9 @@ metsEditor.pageSeparators=" " # Priority list of metadata keys used to display title information in the metadata editors structure and gallery panels metsEditor.titleMetadata=TitleDocMain +#Maximum number of media to be uploaded. +metsEditor.maxUploadedMedia=3 + # ----------------------------------- # backup of metadata configuration # ----------------------------------- diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 8ebfa1e1fbc..6cb62a9c161 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -159,6 +159,7 @@ cannotDeleteProject=Das Projekt kann nicht gel\u00F6scht werden, solange Vor\u00 cannotDeleteTemplate=Die Produktionsvorlage kann nicht gel\u00F6scht werden, da bereits Vor\u00E4nge auf dieser Grundlage erzeugt wurden. changeDocstructType=Typ des Strukturelements \u00E4ndern choice=Aktionen +choose=Ausw\u00E4hlen chooseFile=Datei ausw\u00E4hlen citation.accessTimestamp=Zugriff\: citation.containedIn=In\: @@ -439,6 +440,7 @@ from=von gallery=Galerie generate=Generieren generateImages=Generiere {0} +generateMediaFailed=Die Generierung neu hochgeladener Medien wurde nicht erfolgreich abgeschlossen. generateMissingImages=Das Generieren aller fehlenden Bilder starten generateMissingImagesStarted=Alle fehlenden Bilder werden vom Taskmanager generiert generatesNewspaperProcessesThread=Erzeuge Zeitungsvorg\u00E4nge @@ -579,6 +581,7 @@ manuellSingleWorkflow=manuell, regul\u00E4rer Worklflow massDownload=Massendownload massImport=Massenimport masterpieceProperties=Werkst\u00FCckeigenschaft +mediaUploaded={0} wurde erfolgreich hochgeladen mediaWillBeAssigned=ausgew\u00E4hlte Medien werden zugewiesen. meineAufgabenMsg=W\u00E4hlen Sie eine der unten gelisteten Aufgaben zur Bearbeitung aus. messageAdd=Nachricht hinzuf\u00FCgen @@ -1009,9 +1012,11 @@ uncounted=unnummeriert unlocked=Entsperrt up=hoch updateType=Art der Aktualisierung -upload=Images hochladen +upload=Hochladen uploadFile=Datei hochladen uploadImport=Dateiupload-Import +uploadMedia=Medien hochladen +uploadMediaCompleted=Hochladen und Generieren neuer Medien wurde erfolgreich beendet. uriMalformed=Der URI hat ein falsches Format. url=URL useExistingWorkflow=Bestehenden Workflow verwenden diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index 605177bf8d5..dc97ebcf194 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -163,6 +163,7 @@ cannotDeleteProject=The project cannot be deleted when processes exist in this p cannotDeleteTemplate=This template cannot be deleted because it has already been used to create processes. changeDocstructType=Change docstruct type choice=Actions +choose=Choose chooseFile=Choose file citation.accessTimestamp=Accessed\: citation.containedIn=In\: @@ -451,6 +452,7 @@ from=from gallery=Gallery generate=Generate generateImages=Generating {0} +generateMediaFailed=The generation of newly uploaded media is not successfully completed. generateMissingImages=Start the generation of all missing images generateMissingImagesStarted=The task manager is having all missing images generated generatesNewspaperProcessesThread=Creating newspaper processes @@ -591,6 +593,7 @@ manuellSingleWorkflow=Manual massDownload=Bulk downloads massImport=Mass import masterpieceProperties=workpiece property +mediaUploaded={0} is uploaded successfully mediaWillBeAssigned=selected media will be assigned. meineAufgabenMsg=Please select one of the listed tasks. messageAdd=Add message @@ -1025,9 +1028,11 @@ uncounted=uncounted unlocked=Unlocked up=up updateType=Update type -upload=Upload images +upload=Upload uploadFile=upload file uploadImport=file upload import +uploadMedia=Upload media +uploadMediaCompleted=Uploading and generating new media has been completed successfully. uriMalformed=The URI is malformed. url=URL useExistingWorkflow=Use existing workflow 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 b1a7edbcc9c..185bf806a2d 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -1748,6 +1748,46 @@ form#metadata div label, height: calc(var(--default-double-size) * 3); } +#uploadFileDialog { + padding: 20px; + max-height: 550px; +} + +#uploadFileDialog_content { + box-sizing: border-box; + overflow: visible; + padding: 0; +} + +#uploadFileDialog .ui-fileupload-buttonbar { + float: none; + text-align: right; +} + +#uploadFileDialog .dialogButtonWrapper { + margin-bottom: 10px; +} + +#uploadFileDialogForm\:progressBar .ui-progressbar-value { + background: #85b2cb; +} + +#primefacesmessagedlg .ui-dialog-titlebar { + background: #52B415; + display: block; +} + +#primefacesmessagedlg .ui-dialog-content { + text-align: center; + padding-left: 5px; +} + +#uploadFileDialogForm, +#uploadFileDialogForm\:uploadFileGrid, +#uploadFileDialogForm\:uploadFileGrid_content{ + height: 90%; +} + #metadataAccordion\:metadata .ui-treetable-indent, #metadataAccordion\:metadata .ui-treetable-toggler { float: left; diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/uploadFile.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/uploadFile.xhtml new file mode 100644 index 00000000000..207e0f2cad5 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/dialogs/uploadFile.xhtml @@ -0,0 +1,103 @@ + + + + + + + + +

#{msgs.uploadMedia}

+
+ + + + + +
+
+ +
+ +
+ + + + + +
+ +
+
+
diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml index a260fd963d2..263c211d344 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/metadataEditor/gallery.xhtml @@ -388,6 +388,14 @@ metadataAccordion:logicalMetadataWrapperPanel @(.structureElementDataList) imagePreviewForm:mediaContextMenu"/> + + + +