From 9f3fa279ffd73a8b2146625610e9a73acd0a426d Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Fri, 18 Oct 2024 10:06:47 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Feat:=20handle=20heic=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Aptfile | 3 +- .../front/validator/ExtensionValidator.java | 1 + .../api/pdf/service/DocumentServiceImpl.java | 17 ++++- .../service/DocumentHelperServiceImpl.java | 65 +++++++++++++++++-- .../interfaces/DocumentHelperService.java | 2 + 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/Aptfile b/Aptfile index 58cf173a7..74914b3f9 100644 --- a/Aptfile +++ b/Aptfile @@ -1,2 +1,3 @@ ttf-mscorefonts-installer -tesseract-ocr \ No newline at end of file +tesseract-ocr +imagemagick diff --git a/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java b/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java index a6e3be64c..56dfbc748 100644 --- a/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java +++ b/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java @@ -12,6 +12,7 @@ public class ExtensionValidator implements ConstraintValidator contentTypes = Arrays.asList( "image/png", "image/jpeg", + "image/heif", "application/pdf" ); diff --git a/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java b/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java index 3951b2aa0..f0774f898 100644 --- a/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java +++ b/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java @@ -12,6 +12,7 @@ import fr.dossierfacile.common.enums.FileStatus; import fr.dossierfacile.common.enums.FileStorageStatus; import fr.dossierfacile.common.repository.WatermarkDocumentRepository; +import fr.dossierfacile.common.service.interfaces.DocumentHelperService; import fr.dossierfacile.common.service.interfaces.EncryptionKeyService; import fr.dossierfacile.common.service.interfaces.FileStorageService; import jakarta.servlet.http.HttpServletResponse; @@ -41,6 +42,7 @@ public class DocumentServiceImpl implements DocumentService { private final FileStorageService fileStorageService; private final EncryptionKeyService encryptionKeyService; private final WatermarkDocumentRepository watermarkDocumentRepository; + private final DocumentHelperService documentHelperService; @Override @Transactional @@ -152,7 +154,20 @@ private WatermarkDocument createDocument(DocumentForm documentForm) { .build(); try { - return fileStorageService.upload(multipartFile.getInputStream(), file); + if ("image/heif".equals(multipartFile.getContentType())) { + file.setName(multipartFile.getOriginalFilename().replaceAll("(?i)\\.heic$", "") + ".jpg"); + file.setContentType("image/jpeg"); + + InputStream jpgInputStream = documentHelperService.convertHeicToJpg(multipartFile.getInputStream()); + if (jpgInputStream != null) { + return fileStorageService.upload(jpgInputStream, file); + } else { + throw new IOException("Image could not be saved"); + } + } else { + return fileStorageService.upload(multipartFile.getInputStream(), file); + } + } catch (IOException e) { log.error("Error on file save", e); throw new RuntimeException(e); diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java index cae47a96c..0845926f3 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java @@ -11,13 +11,13 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -26,6 +26,8 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -42,15 +44,30 @@ public class DocumentHelperServiceImpl implements DocumentHelperService { @Transactional @Override public File addFile(MultipartFile multipartFile, Document document) throws IOException { - StorageFile storageFile = StorageFile.builder() - .name(multipartFile.getOriginalFilename()) - .contentType(multipartFile.getContentType()) .size(multipartFile.getSize()) .encryptionKey(encryptionKeyService.getCurrentKey()) .build(); + String originalFilename = multipartFile.getOriginalFilename(); + if (originalFilename == null) { + originalFilename = UUID.randomUUID().toString(); + } + if ("image/heif".equals(multipartFile.getContentType())) { + storageFile.setName(originalFilename.replaceAll("(?i)\\.heic$", "") + ".jpg"); + storageFile.setContentType("image/jpeg"); + + InputStream jpgInputStream = convertHeicToJpg(multipartFile.getInputStream()); + if (jpgInputStream != null) { + storageFile = fileStorageService.upload(jpgInputStream, storageFile); + } else { + throw new IOException("Image could not be saved"); + } + } else { + storageFile.setName(originalFilename); + storageFile.setContentType(multipartFile.getContentType()); + storageFile = fileStorageService.upload(multipartFile.getInputStream(), storageFile); + } - storageFile = fileStorageService.upload(multipartFile.getInputStream(), storageFile); File file = File.builder() .storageFile(storageFile) @@ -64,6 +81,40 @@ public File addFile(MultipartFile multipartFile, Document document) throws IOExc return file; } + @Override + public InputStream convertHeicToJpg(InputStream heicInputStream) throws IOException { + String tmpImageName = UUID.randomUUID().toString(); + java.io.File heicFile = java.io.File.createTempFile(tmpImageName, ".heic"); + + try (FileOutputStream fileOutputStream = new FileOutputStream(heicFile)) { + IOUtils.copy(heicInputStream, fileOutputStream); + } + + java.io.File jpgFile = java.io.File.createTempFile(tmpImageName, ".jpg"); + + // Use ImageMagick to convert .heic to .jpg + ProcessBuilder processBuilder = new ProcessBuilder("convert", heicFile.getAbsolutePath(), jpgFile.getAbsolutePath()); + Process process = processBuilder.start(); + try { + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Erreur lors de la conversion HEIC en JPG avec ImageMagick."); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + InputStream jpgInputStream = new FileInputStream(jpgFile); + + boolean delete = heicFile.delete(); + if (!delete) { + log.error("Could not delete temporary file"); + } + jpgFile.deleteOnExit(); + + return jpgInputStream; + } + @Override public void deleteFiles(Document document) { if (document.getFiles() != null && !document.getFiles().isEmpty()) { @@ -84,7 +135,7 @@ public StorageFile generatePreview(InputStream fileInputStream, String originalN PDFRenderer pdfRenderer = new PDFRenderer(document); BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 200, ImageType.RGB); preview = resizeImage(bufferedImage); - log.info("resize pdf duration : " + (System.currentTimeMillis() - startTime)); + log.info("resize pdf duration : {}", System.currentTimeMillis() - startTime); } } else { preview = resizeImage(ImageIO.read(fileInputStream)); @@ -129,7 +180,7 @@ BufferedImage resizeImage(BufferedImage image) { graphics2D.dispose(); long endTime = System.currentTimeMillis(); long duration = (endTime - startTime); - log.info("resize image duration : " + duration); + log.info("resize image duration : {}", duration); return resizedImage; } diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java index d8aab1ab3..deea61047 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java @@ -15,6 +15,8 @@ public interface DocumentHelperService { */ File addFile(MultipartFile multipartFile, Document document) throws IOException; + InputStream convertHeicToJpg(InputStream heicInputStream) throws IOException; + /** * Delete files contained in document. */ From 9b9f6569afa14bd5c6669bb3da7674ac5ca05c64 Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Fri, 18 Oct 2024 14:28:23 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20Feat:=20extract=20image=20magic?= =?UTF-8?q?k=20cli=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/ImageMagickConfig.java | 14 ++++++++++++++ .../common/service/DocumentHelperServiceImpl.java | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java new file mode 100644 index 000000000..02ff6822a --- /dev/null +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java @@ -0,0 +1,14 @@ +package fr.dossierfacile.common.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ImageMagickConfig { + @Value("${path.imagemagick.cli:/usr/bin/convert}") + private String imageMagickCli; + + public String getImageMagickCli() { + return imageMagickCli; + } +} diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java index 0845926f3..cdc25846a 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java @@ -1,5 +1,6 @@ package fr.dossierfacile.common.service; +import fr.dossierfacile.common.config.ImageMagickConfig; import fr.dossierfacile.common.entity.Document; import fr.dossierfacile.common.entity.File; import fr.dossierfacile.common.entity.StorageFile; @@ -16,6 +17,7 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,6 +42,7 @@ public class DocumentHelperServiceImpl implements DocumentHelperService { private final FileStorageService fileStorageService; private final SharedFileRepository fileRepository; private final EncryptionKeyService encryptionKeyService; + private final ImageMagickConfig imageMagickConfig; @Transactional @Override @@ -93,7 +96,7 @@ public InputStream convertHeicToJpg(InputStream heicInputStream) throws IOExcept java.io.File jpgFile = java.io.File.createTempFile(tmpImageName, ".jpg"); // Use ImageMagick to convert .heic to .jpg - ProcessBuilder processBuilder = new ProcessBuilder("convert", heicFile.getAbsolutePath(), jpgFile.getAbsolutePath()); + ProcessBuilder processBuilder = new ProcessBuilder(imageMagickConfig.getImageMagickCli(), heicFile.getAbsolutePath(), jpgFile.getAbsolutePath()); Process process = processBuilder.start(); try { int exitCode = process.waitFor(); From 35c776196b7f7dcda252cc9a537664e708924687 Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Fri, 18 Oct 2024 14:41:59 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20Feat:=20specify=20attributes=20?= =?UTF-8?q?on=20tmp=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/service/DocumentHelperServiceImpl.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java index cdc25846a..2ff4dd222 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java @@ -32,7 +32,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.List; +import java.util.Set; import java.util.UUID; @Service @@ -87,13 +91,14 @@ public File addFile(MultipartFile multipartFile, Document document) throws IOExc @Override public InputStream convertHeicToJpg(InputStream heicInputStream) throws IOException { String tmpImageName = UUID.randomUUID().toString(); - java.io.File heicFile = java.io.File.createTempFile(tmpImageName, ".heic"); + FileAttribute> attr = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")); + java.io.File heicFile = java.nio.file.Files.createTempFile(tmpImageName, ".heic", attr).toFile(); try (FileOutputStream fileOutputStream = new FileOutputStream(heicFile)) { IOUtils.copy(heicInputStream, fileOutputStream); } - java.io.File jpgFile = java.io.File.createTempFile(tmpImageName, ".jpg"); + java.io.File jpgFile = java.nio.file.Files.createTempFile(tmpImageName, ".jpg", attr).toFile(); // Use ImageMagick to convert .heic to .jpg ProcessBuilder processBuilder = new ProcessBuilder(imageMagickConfig.getImageMagickCli(), heicFile.getAbsolutePath(), jpgFile.getAbsolutePath()); From be9b2cd732366a8b99173d8208a96d59eaca1f22 Mon Sep 17 00:00:00 2001 From: Matthieu Bollot Date: Fri, 18 Oct 2024 14:56:07 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20Feat:=20handle=20interruptedExc?= =?UTF-8?q?eption=20and=20stop=20thread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/service/DocumentHelperServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java index 2ff4dd222..68d694878 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java @@ -109,7 +109,9 @@ public InputStream convertHeicToJpg(InputStream heicInputStream) throws IOExcept throw new IOException("Erreur lors de la conversion HEIC en JPG avec ImageMagick."); } } catch (InterruptedException e) { - throw new RuntimeException(e); + log.error("Interrupted exception", e); + Thread.currentThread().interrupt(); + throw new IOException(e); } InputStream jpgInputStream = new FileInputStream(jpgFile);