refactor: replace RarExtractor and ZipExtractor with a unified SevenZipExtractor implementation

This commit is contained in:
Rodrigo Verdiani 2026-04-23 09:14:54 -03:00
parent 1ed1d380db
commit 35b4caffb6
4 changed files with 138 additions and 109 deletions

12
pom.xml
View File

@ -131,10 +131,14 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.junrar</groupId> <groupId>net.sf.sevenzipjbinding</groupId>
<artifactId>junrar</artifactId> <artifactId>sevenzipjbinding</artifactId>
<version>7.5.8</version> <version>16.02-2.01</version>
<scope>compile</scope> </dependency>
<dependency>
<groupId>net.sf.sevenzipjbinding</groupId>
<artifactId>sevenzipjbinding-all-platforms</artifactId>
<version>16.02-2.01</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -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<String, byte[]> extract(InputStream stream) throws Exception {
Map<String, byte[]> 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;
}
}

View File

@ -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<String, byte[]> extract(InputStream stream) throws Exception {
Map<String, byte[]> 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<String, byte[]> entryMap;
private int currentIndex;
private ByteArrayOutputStream currentOs;
public ExtractCallback(IInArchive inArchive, Map<String, byte[]> 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) {}
}
}

View File

@ -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<String, byte[]> extract(InputStream stream) throws Exception {
Map<String, byte[]> 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;
}
}