From 35b4caffb62df6a83a617a186df3648c22f197c6 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Thu, 23 Apr 2026 09:14:54 -0300 Subject: [PATCH] refactor: replace RarExtractor and ZipExtractor with a unified SevenZipExtractor implementation --- pom.xml | 12 +- .../content/service/archive/RarExtractor.java | 58 -------- .../service/archive/SevenZipExtractor.java | 130 ++++++++++++++++++ .../content/service/archive/ZipExtractor.java | 47 ------- 4 files changed, 138 insertions(+), 109 deletions(-) delete mode 100644 src/main/java/com/magamochi/content/service/archive/RarExtractor.java create mode 100644 src/main/java/com/magamochi/content/service/archive/SevenZipExtractor.java delete mode 100644 src/main/java/com/magamochi/content/service/archive/ZipExtractor.java diff --git a/pom.xml b/pom.xml index bea9041..8807d1f 100644 --- a/pom.xml +++ b/pom.xml @@ -131,10 +131,14 @@ compile - com.github.junrar - junrar - 7.5.8 - compile + net.sf.sevenzipjbinding + sevenzipjbinding + 16.02-2.01 + + + net.sf.sevenzipjbinding + sevenzipjbinding-all-platforms + 16.02-2.01 org.springframework.boot diff --git a/src/main/java/com/magamochi/content/service/archive/RarExtractor.java b/src/main/java/com/magamochi/content/service/archive/RarExtractor.java deleted file mode 100644 index 72f1da7..0000000 --- a/src/main/java/com/magamochi/content/service/archive/RarExtractor.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.magamochi.content.service.archive; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - -import com.github.junrar.Archive; -import com.github.junrar.rarfile.FileHeader; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.Map; -import java.util.TreeMap; -import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator; -import org.springframework.stereotype.Component; - -@Component -public class RarExtractor implements ArchiveExtractor { - - @Override - public boolean supports(String mimeType) { - if (isNull(mimeType)) { - return false; - } - - return mimeType.startsWith("application/x-rar-compressed") - || mimeType.startsWith("application/rar"); - } - - @Override - public Map extract(InputStream stream) throws Exception { - Map entryMap = - new TreeMap<>(CaseInsensitiveSimpleNaturalComparator.getInstance()); - - var tempFile = Files.createTempFile("manga_import_rar_", ".rar"); - - try { - Files.copy(stream, tempFile, StandardCopyOption.REPLACE_EXISTING); - - try (var archive = new Archive(tempFile.toFile())) { - FileHeader fileHeader; - while (nonNull(fileHeader = archive.nextFileHeader())) { - if (fileHeader.isDirectory()) { - continue; - } - - var os = new ByteArrayOutputStream(); - archive.extractFile(fileHeader, os); - entryMap.put(fileHeader.getFileName(), os.toByteArray()); - } - } - } finally { - Files.deleteIfExists(tempFile); - } - - return entryMap; - } -} diff --git a/src/main/java/com/magamochi/content/service/archive/SevenZipExtractor.java b/src/main/java/com/magamochi/content/service/archive/SevenZipExtractor.java new file mode 100644 index 0000000..f92c6c4 --- /dev/null +++ b/src/main/java/com/magamochi/content/service/archive/SevenZipExtractor.java @@ -0,0 +1,130 @@ +package com.magamochi.content.service.archive; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Map; +import java.util.TreeMap; +import lombok.extern.slf4j.Slf4j; +import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator; +import net.sf.sevenzipjbinding.ExtractAskMode; +import net.sf.sevenzipjbinding.ExtractOperationResult; +import net.sf.sevenzipjbinding.IArchiveExtractCallback; +import net.sf.sevenzipjbinding.IInArchive; +import net.sf.sevenzipjbinding.ISequentialOutStream; +import net.sf.sevenzipjbinding.PropID; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.SevenZipException; +import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class SevenZipExtractor implements ArchiveExtractor { + public SevenZipExtractor() { + try { + SevenZip.initSevenZipFromPlatformJAR(); + log.info("Successfully initialized SevenZipJBinding for multi-platform archive extraction."); + } catch (net.sf.sevenzipjbinding.SevenZipNativeInitializationException e) { + log.error( + "Failed to initialize SevenZipJBinding. Native bindings may be missing or incompatible.", + e); + } + } + + @Override + public boolean supports(String mimeType) { + if (isNull(mimeType)) { + return false; + } + + return mimeType.startsWith("application/zip") + || mimeType.startsWith("application/x-rar-compressed") + || mimeType.startsWith("application/rar") + || mimeType.startsWith("application/x-7z-compressed"); + } + + @Override + public Map extract(InputStream stream) throws Exception { + Map entryMap = + new TreeMap<>(CaseInsensitiveSimpleNaturalComparator.getInstance()); + var tempFile = Files.createTempFile("manga_import_archive_", ".tmp"); + + try { + Files.copy(stream, tempFile, StandardCopyOption.REPLACE_EXISTING); + + try (var randomAccessFile = new RandomAccessFile(tempFile.toFile(), "r"); + var inStream = new RandomAccessFileInStream(randomAccessFile); + var inArchive = SevenZip.openInArchive(null, inStream)) { + + inArchive.extract(null, false, new ExtractCallback(inArchive, entryMap)); + } + } finally { + Files.deleteIfExists(tempFile); + } + + return entryMap; + } + + private static class ExtractCallback implements IArchiveExtractCallback { + private final IInArchive inArchive; + private final Map entryMap; + private int currentIndex; + private ByteArrayOutputStream currentOs; + + public ExtractCallback(IInArchive inArchive, Map entryMap) { + this.inArchive = inArchive; + this.entryMap = entryMap; + } + + @Override + public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) + throws SevenZipException { + this.currentIndex = index; + + if (!extractAskMode.equals(ExtractAskMode.EXTRACT)) { + return null; + } + + var isFolder = (Boolean) inArchive.getProperty(index, PropID.IS_FOLDER); + if (nonNull(isFolder) && isFolder) { + return null; + } + + currentOs = new ByteArrayOutputStream(); + return data -> { + try { + currentOs.write(data); + return data.length; + } catch (Exception e) { + throw new SevenZipException("Error writing to stream", e); + } + }; + } + + @Override + public void prepareOperation(ExtractAskMode extractAskMode) {} + + @Override + public void setOperationResult(ExtractOperationResult extractOperationResult) + throws SevenZipException { + if (extractOperationResult.equals(ExtractOperationResult.OK) && nonNull(currentOs)) { + var path = (String) inArchive.getProperty(currentIndex, PropID.PATH); + entryMap.put(path, currentOs.toByteArray()); + } + + currentOs = null; + } + + @Override + public void setTotal(long total) {} + + @Override + public void setCompleted(long complete) {} + } +} diff --git a/src/main/java/com/magamochi/content/service/archive/ZipExtractor.java b/src/main/java/com/magamochi/content/service/archive/ZipExtractor.java deleted file mode 100644 index 1725c6f..0000000 --- a/src/main/java/com/magamochi/content/service/archive/ZipExtractor.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.magamochi.content.service.archive; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.Map; -import java.util.TreeMap; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator; -import org.springframework.stereotype.Component; - -@Component -public class ZipExtractor implements ArchiveExtractor { - @Override - public boolean supports(String mimeType) { - if (isNull(mimeType)) { - return false; - } - - return mimeType.startsWith("application/zip"); - } - - @Override - public Map extract(InputStream stream) throws Exception { - Map entryMap = - new TreeMap<>(CaseInsensitiveSimpleNaturalComparator.getInstance()); - - try (var zis = new ZipInputStream(stream)) { - ZipEntry entry; - while (nonNull(entry = zis.getNextEntry())) { - if (entry.isDirectory()) { - continue; - } - - var os = new ByteArrayOutputStream(); - zis.transferTo(os); - entryMap.put(entry.getName(), os.toByteArray()); - zis.closeEntry(); - } - } - - return entryMap; - } -}