Replace extraction backend with SevenZipJBinding + Zip4j JVM sidecar
Build and Release / build (push) Has been cancelled

- New JVM sidecar (resources/extractor-jvm/) using SevenZipJBinding for
  RAR/7z/TAR and Zip4j for ZIP multipart, matching JDownloader 2 stack
- Auto/JVM/Legacy backend modes via RD_EXTRACT_BACKEND env variable
- Fallback to legacy UnRAR/7z when JVM runtime unavailable
- Fix isJvmRuntimeMissingError false positives on valid extraction errors
- Cache JVM layout resolution to avoid repeated filesystem checks
- Route nested ZIP extraction through JVM backend consistently

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe
2026-03-03 02:08:42 +01:00
co-authored by Claude Opus 4.6
parent 3ee3af03cf
commit de369f3bcd
22 changed files with 1888 additions and 9 deletions
+22
View File
@@ -0,0 +1,22 @@
# JVM extractor runtime
This directory contains the Java sidecar runtime used by `src/main/extractor.ts`.
## Included backends
- `sevenzipjbinding` for the primary extraction path (RAR/7z/ZIP and others)
- `zip4j` for ZIP multipart handling (JD-style split ZIP behavior)
## Layout
- `classes/` compiled `JBindExtractorMain` classes
- `lib/` runtime jars required by the sidecar
- `src/` Java source for the sidecar
## Rebuild notes
The checked-in classes are Java 8 compatible and built from:
`resources/extractor-jvm/src/com/sucukdeluxe/extractor/JBindExtractorMain.java`
If you need to rebuild, compile against the jars in `lib/` with a Java 8-compatible compiler.
@@ -0,0 +1,12 @@
Bundled JVM extractor dependencies:
1) sevenzipjbinding (16.02-2.01)
- Maven artifact: net.sf.sevenzipjbinding:sevenzipjbinding
- Maven artifact: net.sf.sevenzipjbinding:sevenzipjbinding-all-platforms
- Upstream: https://sevenzipjbind.sourceforge.net/
2) zip4j (2.11.5)
- Maven artifact: net.lingala.zip4j:zip4j
- Upstream: https://github.com/srikanth-lingala/zip4j
Please review upstream licenses and notices before redistribution.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,856 @@
package com.sucukdeluxe.extractor;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IArchiveOpenCallback;
import net.sf.sevenzipjbinding.IArchiveOpenVolumeCallback;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.ISequentialOutStream;
import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.impl.VolumedArchiveInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public final class JBindExtractorMain {
private static final int BUFFER_SIZE = 64 * 1024;
private static final Pattern NUMBERED_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.zip\\.\\d{3}$");
private static final Pattern OLD_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.z\\d{2,3}$");
private static final Pattern SEVEN_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.7z\\.001$");
private JBindExtractorMain() {
}
public static void main(String[] args) {
int exit = 1;
try {
ExtractionRequest request = parseArgs(args);
exit = runExtraction(request);
} catch (IllegalArgumentException error) {
emitError("Argumentfehler: " + safeMessage(error));
exit = 2;
} catch (Throwable error) {
emitError(safeMessage(error));
exit = 1;
}
System.exit(exit);
}
private static int runExtraction(ExtractionRequest request) throws Exception {
List<String> passwords = normalizePasswords(request.passwords);
Exception lastError = null;
boolean hadWrongPassword = false;
for (String password : passwords) {
try {
extractSingle(request, password);
emitPassword(password);
emitDone();
return 0;
} catch (WrongPasswordException wrongPassword) {
hadWrongPassword = true;
lastError = wrongPassword;
} catch (Exception error) {
lastError = error;
break;
}
}
if (hadWrongPassword && (lastError instanceof WrongPasswordException)) {
emitError("Falsches Archiv-Passwort");
return 1;
}
if (lastError != null) {
throw lastError;
}
emitError("Entpacken fehlgeschlagen");
return 1;
}
private static void extractSingle(ExtractionRequest request, String password) throws Exception {
Backend backend = request.backend;
if (backend == Backend.AUTO) {
backend = shouldUseZip4j(request.archiveFile) ? Backend.ZIP4J : Backend.SEVENZIPJBIND;
}
emitBackend(backend);
if (backend == Backend.ZIP4J) {
extractWithZip4j(request, password);
return;
}
extractWithSevenZip(request, password);
}
private static void extractWithZip4j(ExtractionRequest request, String password) throws Exception {
ZipFile zipFile = new ZipFile(request.archiveFile);
try {
if (password != null && password.length() > 0) {
zipFile.setPassword(password.toCharArray());
}
List<FileHeader> fileHeaders = zipFile.getFileHeaders();
if (fileHeaders == null) {
fileHeaders = new ArrayList<FileHeader>();
}
long totalUnits = 0;
boolean encrypted = false;
for (FileHeader header : fileHeaders) {
if (header == null || header.isDirectory()) {
continue;
}
encrypted = encrypted || header.isEncrypted();
totalUnits += safeSize(header.getUncompressedSize());
}
ProgressTracker progress = new ProgressTracker(totalUnits);
progress.emitStart();
Set<String> reserved = new HashSet<String>();
for (FileHeader header : fileHeaders) {
if (header == null) {
continue;
}
String entryName = normalizeEntryName(header.getFileName(), "file");
if (header.isDirectory()) {
File dir = resolveDirectory(request.targetDir, entryName);
ensureDirectory(dir);
reserved.add(pathKey(dir));
continue;
}
long itemUnits = safeSize(header.getUncompressedSize());
File output = resolveOutputFile(request.targetDir, entryName, request.conflictMode, reserved);
if (output == null) {
progress.advance(itemUnits);
continue;
}
ensureDirectory(output.getParentFile());
long[] remaining = new long[] { itemUnits };
try {
InputStream in = zipFile.getInputStream(header);
OutputStream out = new FileOutputStream(output);
try {
byte[] buffer = new byte[BUFFER_SIZE];
while (true) {
int read = in.read(buffer);
if (read < 0) {
break;
}
if (read == 0) {
continue;
}
out.write(buffer, 0, read);
long accounted = Math.min(remaining[0], (long) read);
remaining[0] -= accounted;
progress.advance(accounted);
}
} finally {
try {
out.close();
} catch (Throwable ignored) {
}
try {
in.close();
} catch (Throwable ignored) {
}
}
if (remaining[0] > 0) {
progress.advance(remaining[0]);
}
long modified = header.getLastModifiedTimeEpoch();
if (modified > 0) {
output.setLastModified(modified);
}
} catch (ZipException error) {
if (isWrongPassword(error, encrypted)) {
throw new WrongPasswordException(error);
}
throw error;
}
}
progress.emitDone();
} finally {
try {
zipFile.close();
} catch (Throwable ignored) {
}
}
}
private static void extractWithSevenZip(ExtractionRequest request, String password) throws Exception {
SevenZipArchiveContext context = null;
try {
context = openSevenZipArchive(request.archiveFile, password);
IInArchive archive = context.archive;
ISimpleInArchive simple = archive.getSimpleInterface();
ISimpleInArchiveItem[] items = simple.getArchiveItems();
long totalUnits = 0;
boolean encrypted = false;
for (ISimpleInArchiveItem item : items) {
if (item == null || item.isFolder()) {
continue;
}
try {
encrypted = encrypted || item.isEncrypted();
} catch (Throwable ignored) {
// ignore encrypted flag read issues
}
totalUnits += safeSize(item.getSize());
}
ProgressTracker progress = new ProgressTracker(totalUnits);
progress.emitStart();
Set<String> reserved = new HashSet<String>();
for (ISimpleInArchiveItem item : items) {
if (item == null) {
continue;
}
String entryName = normalizeEntryName(item.getPath(), "item-" + item.getItemIndex());
if (item.isFolder()) {
File dir = resolveDirectory(request.targetDir, entryName);
ensureDirectory(dir);
reserved.add(pathKey(dir));
continue;
}
long itemUnits = safeSize(item.getSize());
File output = resolveOutputFile(request.targetDir, entryName, request.conflictMode, reserved);
if (output == null) {
progress.advance(itemUnits);
continue;
}
ensureDirectory(output.getParentFile());
final FileOutputStream out = new FileOutputStream(output);
final long[] remaining = new long[] { itemUnits };
try {
ExtractOperationResult result = item.extractSlow(new ISequentialOutStream() {
@Override
public int write(byte[] data) throws SevenZipException {
if (data == null || data.length == 0) {
return 0;
}
try {
out.write(data);
} catch (IOException error) {
throw new SevenZipException("Fehler beim Schreiben: " + error.getMessage(), error);
}
long accounted = Math.min(remaining[0], (long) data.length);
remaining[0] -= accounted;
progress.advance(accounted);
return data.length;
}
});
if (remaining[0] > 0) {
progress.advance(remaining[0]);
}
if (result != ExtractOperationResult.OK) {
if (isPasswordFailure(result, encrypted)) {
throw new WrongPasswordException(new IOException("Falsches Passwort"));
}
throw new IOException("7z-Fehler: " + result.name());
}
} catch (SevenZipException error) {
if (looksLikeWrongPassword(error, encrypted)) {
throw new WrongPasswordException(error);
}
throw error;
} finally {
try {
out.close();
} catch (Throwable ignored) {
}
}
try {
java.util.Date modified = item.getLastWriteTime();
if (modified != null) {
output.setLastModified(modified.getTime());
}
} catch (Throwable ignored) {
// best effort
}
}
progress.emitDone();
} finally {
if (context != null) {
context.close();
}
}
}
private static SevenZipArchiveContext openSevenZipArchive(File archiveFile, String password) throws Exception {
String nameLower = archiveFile.getName().toLowerCase(Locale.ROOT);
String effectivePassword = password == null ? "" : password;
SevenZipVolumeCallback callback = new SevenZipVolumeCallback(archiveFile, effectivePassword);
if (SEVEN_ZIP_SPLIT_RE.matcher(nameLower).matches()) {
VolumedArchiveInStream volumed = new VolumedArchiveInStream(archiveFile.getName(), callback);
IInArchive archive = SevenZip.openInArchive(null, volumed, callback);
return new SevenZipArchiveContext(archive, null, volumed, callback);
}
RandomAccessFile raf = new RandomAccessFile(archiveFile, "r");
RandomAccessFileInStream stream = new RandomAccessFileInStream(raf);
IInArchive archive = SevenZip.openInArchive(null, stream, callback);
return new SevenZipArchiveContext(archive, stream, null, callback);
}
private static boolean isWrongPassword(ZipException error, boolean encrypted) {
if (error == null) {
return false;
}
if (error.getType() == ZipException.Type.WRONG_PASSWORD) {
return true;
}
String text = safeMessage(error).toLowerCase(Locale.ROOT);
if (text.contains("wrong password") || text.contains("falsches passwort")) {
return true;
}
return encrypted && (text.contains("checksum") || text.contains("crc") || text.contains("password"));
}
private static boolean isPasswordFailure(ExtractOperationResult result, boolean encrypted) {
if (!encrypted || result == null) {
return false;
}
return result == ExtractOperationResult.CRCERROR || result == ExtractOperationResult.DATAERROR;
}
private static boolean looksLikeWrongPassword(Throwable error, boolean encrypted) {
if (error == null) {
return false;
}
String text = safeMessage(error).toLowerCase(Locale.ROOT);
if (text.contains("wrong password") || text.contains("falsches passwort")) {
return true;
}
return encrypted && (text.contains("crc") || text.contains("data error") || text.contains("checksum"));
}
private static boolean shouldUseZip4j(File archiveFile) {
String name = archiveFile.getName().toLowerCase(Locale.ROOT);
if (NUMBERED_ZIP_SPLIT_RE.matcher(name).matches()) {
return true;
}
if (OLD_ZIP_SPLIT_RE.matcher(name).matches()) {
return true;
}
if (name.endsWith(".zip")) {
File parent = archiveFile.getParentFile();
if (parent == null || !parent.exists()) {
return false;
}
String stem = archiveFile.getName().substring(0, archiveFile.getName().length() - 4);
File[] siblings = parent.listFiles();
if (siblings == null) {
return false;
}
String prefix = (stem + ".z").toLowerCase(Locale.ROOT);
for (File sibling : siblings) {
String siblingName = sibling.getName().toLowerCase(Locale.ROOT);
if (!sibling.isFile()) {
continue;
}
if (siblingName.startsWith(prefix) && siblingName.length() >= prefix.length() + 2) {
String suffix = siblingName.substring(prefix.length());
if (suffix.matches("\\d{2,3}")) {
return true;
}
}
}
}
return false;
}
private static File resolveDirectory(File targetDir, String entryName) throws IOException {
File directory = secureResolve(targetDir, entryName);
return directory;
}
private static File resolveOutputFile(File targetDir, String entryName, ConflictMode conflictMode, Set<String> reserved) throws IOException {
File base = secureResolve(targetDir, entryName);
String key = pathKey(base);
boolean exists = base.exists() || reserved.contains(key);
if (!exists) {
reserved.add(key);
return base;
}
if (conflictMode == ConflictMode.SKIP) {
return null;
}
if (conflictMode == ConflictMode.OVERWRITE) {
if (base.exists()) {
deleteRecursively(base);
}
reserved.add(key);
return base;
}
File parent = base.getParentFile();
String fileName = base.getName();
int dot = fileName.lastIndexOf('.');
String stem = dot > 0 ? fileName.substring(0, dot) : fileName;
String ext = dot > 0 ? fileName.substring(dot) : "";
int counter = 1;
while (counter <= 10000) {
String candidateName = stem + " (" + counter + ")" + ext;
File candidate = new File(parent, candidateName);
String candidateKey = pathKey(candidate);
if (!candidate.exists() && !reserved.contains(candidateKey)) {
reserved.add(candidateKey);
return candidate;
}
counter += 1;
}
throw new IOException("Rename-Limit erreicht fur " + entryName);
}
private static void deleteRecursively(File file) throws IOException {
if (file == null || !file.exists()) {
return;
}
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
}
if (!file.delete()) {
throw new IOException("Konnte Datei nicht uberschreiben: " + file.getAbsolutePath());
}
}
private static File secureResolve(File targetDir, String entryName) throws IOException {
String normalized = normalizeEntryName(entryName, "file");
while (normalized.startsWith("/")) {
normalized = normalized.substring(1);
}
while (normalized.startsWith("\\")) {
normalized = normalized.substring(1);
}
if (normalized.matches("^[a-zA-Z]:.*")) {
normalized = normalized.substring(2);
}
File targetCanonical = targetDir.getCanonicalFile();
File output = new File(targetCanonical, normalized);
File outputCanonical = output.getCanonicalFile();
String targetPath = targetCanonical.getPath();
String outputPath = outputCanonical.getPath();
String targetPathNorm = isWindows() ? targetPath.toLowerCase(Locale.ROOT) : targetPath;
String outputPathNorm = isWindows() ? outputPath.toLowerCase(Locale.ROOT) : outputPath;
if (!outputPathNorm.equals(targetPathNorm) && !outputPathNorm.startsWith(targetPathNorm + File.separator)) {
throw new IOException("Path Traversal blockiert: " + entryName);
}
return outputCanonical;
}
private static String normalizeEntryName(String value, String fallback) {
String entry = value == null ? "" : value.trim();
if (entry.length() == 0) {
return fallback;
}
entry = entry.replace('\\', '/');
while (entry.startsWith("./")) {
entry = entry.substring(2);
}
if (entry.length() == 0) {
return fallback;
}
return entry;
}
private static long safeSize(Long value) {
if (value == null) {
return 1;
}
long size = value.longValue();
if (size <= 0) {
return 1;
}
return size;
}
private static void ensureDirectory(File dir) throws IOException {
if (dir == null) {
return;
}
if (dir.exists()) {
if (!dir.isDirectory()) {
throw new IOException("Pfad ist keine Directory: " + dir.getAbsolutePath());
}
return;
}
if (!dir.mkdirs() && !dir.isDirectory()) {
throw new IOException("Verzeichnis konnte nicht erstellt werden: " + dir.getAbsolutePath());
}
}
private static String pathKey(File file) {
String value = file.getAbsolutePath();
if (isWindows()) {
value = value.toLowerCase(Locale.ROOT);
}
return value;
}
private static boolean isWindows() {
String osName = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
return osName.contains("win");
}
private static List<String> normalizePasswords(List<String> input) {
LinkedHashSet<String> deduped = new LinkedHashSet<String>();
deduped.add("");
if (input != null) {
for (String value : input) {
deduped.add(value == null ? "" : value);
}
}
return new ArrayList<String>(deduped);
}
private static ExtractionRequest parseArgs(String[] args) {
ExtractionRequest request = new ExtractionRequest();
int index = 0;
while (index < args.length) {
String key = args[index];
if ("--archive".equals(key)) {
request.archiveFile = new File(readNext(args, ++index, key));
} else if ("--target".equals(key)) {
request.targetDir = new File(readNext(args, ++index, key));
} else if ("--conflict".equals(key)) {
request.conflictMode = ConflictMode.fromValue(readNext(args, ++index, key));
} else if ("--backend".equals(key)) {
request.backend = Backend.fromValue(readNext(args, ++index, key));
} else if ("--password".equals(key)) {
request.passwords.add(readNext(args, ++index, key));
} else {
throw new IllegalArgumentException("Unbekanntes Argument: " + key);
}
index += 1;
}
if (request.archiveFile == null) {
throw new IllegalArgumentException("--archive fehlt");
}
if (request.targetDir == null) {
throw new IllegalArgumentException("--target fehlt");
}
if (!request.archiveFile.exists() || !request.archiveFile.isFile()) {
throw new IllegalArgumentException("Archiv nicht gefunden: " + request.archiveFile.getAbsolutePath());
}
return request;
}
private static String readNext(String[] args, int index, String key) {
if (index >= args.length) {
throw new IllegalArgumentException("Wert fehlt fur " + key);
}
return args[index];
}
private static String safeMessage(Throwable error) {
if (error == null) {
return "Unbekannter Fehler";
}
String message = error.getMessage();
if (message == null || message.trim().length() == 0) {
message = error.toString();
}
return message.replace('\n', ' ').replace('\r', ' ').trim();
}
private static void emitBackend(Backend backend) {
System.out.println("RD_BACKEND " + backend.value);
}
private static void emitPassword(String password) {
String encoded = Base64.getEncoder().encodeToString((password == null ? "" : password).getBytes(StandardCharsets.UTF_8));
System.out.println("RD_PASSWORD " + encoded);
}
private static void emitDone() {
System.out.println("RD_DONE");
}
private static void emitError(String message) {
System.err.println("RD_ERROR " + message);
}
private enum Backend {
AUTO("auto"),
SEVENZIPJBIND("7zjbinding"),
ZIP4J("zip4j");
private final String value;
Backend(String value) {
this.value = value;
}
static Backend fromValue(String raw) {
String value = raw == null ? "" : raw.trim().toLowerCase(Locale.ROOT);
if ("auto".equals(value)) {
return AUTO;
}
if ("7zjb".equals(value) || "7zjbinding".equals(value) || "sevenzipjbinding".equals(value)) {
return SEVENZIPJBIND;
}
if ("zip4j".equals(value)) {
return ZIP4J;
}
throw new IllegalArgumentException("Ungueltiger Backend-Wert: " + raw);
}
}
private enum ConflictMode {
OVERWRITE,
SKIP,
RENAME;
static ConflictMode fromValue(String raw) {
String value = raw == null ? "" : raw.trim().toLowerCase(Locale.ROOT);
if ("overwrite".equals(value)) {
return OVERWRITE;
}
if ("skip".equals(value) || "ask".equals(value)) {
return SKIP;
}
if ("rename".equals(value)) {
return RENAME;
}
throw new IllegalArgumentException("Ungueltiger Conflict-Wert: " + raw);
}
}
private static final class ExtractionRequest {
private File archiveFile;
private File targetDir;
private ConflictMode conflictMode = ConflictMode.SKIP;
private Backend backend = Backend.AUTO;
private final List<String> passwords = new ArrayList<String>();
}
private static final class WrongPasswordException extends Exception {
private static final long serialVersionUID = 1L;
WrongPasswordException(Throwable cause) {
super(cause);
}
}
private static final class ProgressTracker {
private final long total;
private long completed;
private int lastPercent = -1;
ProgressTracker(long totalUnits) {
this.total = Math.max(1L, totalUnits);
this.completed = 0L;
}
synchronized void emitStart() {
emitPercent(0);
}
synchronized void advance(long units) {
if (units <= 0) {
return;
}
completed += units;
if (completed > total) {
completed = total;
}
int percent = (int) Math.min(100L, Math.max(0L, (completed * 100L) / total));
emitPercent(percent);
}
synchronized void emitDone() {
completed = total;
emitPercent(100);
}
private void emitPercent(int percent) {
int bounded = Math.max(0, Math.min(100, percent));
if (bounded == lastPercent) {
return;
}
lastPercent = bounded;
System.out.println("RD_PROGRESS " + bounded + "%");
}
}
private static final class SevenZipArchiveContext implements Closeable {
private final IInArchive archive;
private final IInStream rootStream;
private final VolumedArchiveInStream volumedArchiveInStream;
private final SevenZipVolumeCallback callback;
SevenZipArchiveContext(IInArchive archive, IInStream rootStream, VolumedArchiveInStream volumedArchiveInStream, SevenZipVolumeCallback callback) {
this.archive = archive;
this.rootStream = rootStream;
this.volumedArchiveInStream = volumedArchiveInStream;
this.callback = callback;
}
@Override
public void close() {
if (archive != null) {
try {
archive.close();
} catch (Throwable ignored) {
}
}
if (rootStream != null) {
try {
rootStream.close();
} catch (Throwable ignored) {
}
}
if (volumedArchiveInStream != null) {
try {
volumedArchiveInStream.close();
} catch (Throwable ignored) {
}
}
if (callback != null) {
callback.close();
}
}
}
private static final class SevenZipVolumeCallback implements IArchiveOpenCallback, IArchiveOpenVolumeCallback, ICryptoGetTextPassword, Closeable {
private final File archiveDir;
private final String firstFileName;
private final String password;
private final Map<String, RandomAccessFile> openRafs = new HashMap<String, RandomAccessFile>();
SevenZipVolumeCallback(File archiveFile, String password) {
this.archiveDir = archiveFile.getAbsoluteFile().getParentFile();
this.firstFileName = archiveFile.getName();
this.password = password == null ? "" : password;
}
@Override
public Object getProperty(PropID propID) {
if (propID == PropID.NAME) {
return firstFileName;
}
return null;
}
@Override
public IInStream getStream(String filename) throws SevenZipException {
File file = resolveVolumeFile(filename);
if (file == null || !file.exists() || !file.isFile()) {
return null;
}
try {
String key = pathKey(file);
RandomAccessFile raf = openRafs.get(key);
if (raf == null) {
raf = new RandomAccessFile(file, "r");
openRafs.put(key, raf);
}
raf.seek(0L);
return new RandomAccessFileInStream(raf);
} catch (IOException error) {
throw new SevenZipException("Volume konnte nicht geoffnet werden: " + filename, error);
}
}
@Override
public void setTotal(Long files, Long bytes) {
// no-op
}
@Override
public void setCompleted(Long files, Long bytes) {
// no-op
}
@Override
public String cryptoGetTextPassword() {
return password;
}
private File resolveVolumeFile(String filename) {
if (filename == null || filename.trim().length() == 0) {
return null;
}
File direct = new File(filename);
if (direct.isAbsolute() && direct.exists()) {
return direct;
}
if (archiveDir != null) {
File relative = new File(archiveDir, filename);
if (relative.exists()) {
return relative;
}
File[] siblings = archiveDir.listFiles();
if (siblings != null) {
for (File sibling : siblings) {
if (!sibling.isFile()) {
continue;
}
if (sibling.getName().equalsIgnoreCase(filename)) {
return sibling;
}
}
}
}
return direct.exists() ? direct : null;
}
@Override
public void close() {
for (RandomAccessFile raf : openRafs.values()) {
try {
raf.close();
} catch (Throwable ignored) {
}
}
openRafs.clear();
}
}
}