diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$1.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$1.class index 55f87ba..9c99eaf 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$1.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$1.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$Backend.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$Backend.class index 2c503da..0951869 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$Backend.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$Backend.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ConflictMode.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ConflictMode.class index 6376bb2..3770d21 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ConflictMode.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ConflictMode.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ExtractionRequest.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ExtractionRequest.class index 627e041..619418a 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ExtractionRequest.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ExtractionRequest.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ProgressTracker.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ProgressTracker.class index 025b84d..1623cea 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ProgressTracker.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$ProgressTracker.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipArchiveContext.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipArchiveContext.class index 4beedba..58bd4c7 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipArchiveContext.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipArchiveContext.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipVolumeCallback.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipVolumeCallback.class index 4e1cd8a..b6da3eb 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipVolumeCallback.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$SevenZipVolumeCallback.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$WrongPasswordException.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$WrongPasswordException.class index d20b173..e5c3d6b 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$WrongPasswordException.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain$WrongPasswordException.class differ diff --git a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain.class b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain.class index a6fa276..321cb68 100644 Binary files a/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain.class and b/resources/extractor-jvm/classes/com/sucukdeluxe/extractor/JBindExtractorMain.class differ diff --git a/resources/extractor-jvm/src/com/sucukdeluxe/extractor/JBindExtractorMain.java b/resources/extractor-jvm/src/com/sucukdeluxe/extractor/JBindExtractorMain.java index 7ed46ec..64e4dca 100644 --- a/resources/extractor-jvm/src/com/sucukdeluxe/extractor/JBindExtractorMain.java +++ b/resources/extractor-jvm/src/com/sucukdeluxe/extractor/JBindExtractorMain.java @@ -11,7 +11,6 @@ import net.sf.sevenzipjbinding.IInStream; import net.sf.sevenzipjbinding.ISequentialOutStream; import net.sf.sevenzipjbinding.ICryptoGetTextPassword; import net.sf.sevenzipjbinding.PropID; -import net.sf.sevenzipjbinding.ArchiveFormat; import net.sf.sevenzipjbinding.SevenZip; import net.sf.sevenzipjbinding.SevenZipException; import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; @@ -43,8 +42,6 @@ public final class JBindExtractorMain { 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 static final Pattern RAR_MULTIPART_RE = Pattern.compile("(?i).*\\.part\\d+\\.rar$"); - private static final Pattern RAR_OLDSPLIT_RE = Pattern.compile("(?i).*\\.r\\d{2,3}$"); private static volatile boolean sevenZipInitialized = false; private JBindExtractorMain() { @@ -330,78 +327,21 @@ public final class JBindExtractorMain { SevenZipVolumeCallback callback = new SevenZipVolumeCallback(archiveFile, effectivePassword); // VolumedArchiveInStream is ONLY for .7z.001 split archives. - // It internally checks for the ".7z.001" suffix and rejects everything else. 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); } - // Multi-part RAR (.part1.rar, .part2.rar or old-style .rar/.r01/.r02): - // The first stream MUST be obtained via the callback so the volume name - // tracker is properly initialized. 7z-JBinding uses getProperty(NAME) - // to compute subsequent volume filenames. - boolean isMultiPartRar = RAR_MULTIPART_RE.matcher(nameLower).matches() - || hasOldStyleRarSplits(archiveFile); - - if (isMultiPartRar) { - IInStream inStream = callback.getStream(archiveFile.getAbsolutePath()); - if (inStream == null) { - throw new IOException("Archiv konnte nicht geoeffnet werden: " + archiveFile.getAbsolutePath()); - } - // Try RAR5 first (modern), then RAR4, then auto-detect - Exception lastError = null; - ArchiveFormat[] rarFormats = { ArchiveFormat.RAR5, ArchiveFormat.RAR, null }; - for (ArchiveFormat fmt : rarFormats) { - try { - inStream.seek(0L, 0); - IInArchive archive = SevenZip.openInArchive(fmt, inStream, callback); - return new SevenZipArchiveContext(archive, null, null, callback); - } catch (Exception e) { - lastError = e; - } - } - callback.close(); - throw lastError != null ? lastError : new IOException("Archiv konnte nicht geoeffnet werden"); - } - - // Single-file archives: open directly with auto-detection + // All other archives (including multi-part RAR): use RandomAccessFileInStream + // with auto-detection. The IArchiveOpenVolumeCallback handles additional + // volumes when 7z-JBinding requests them. 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 hasOldStyleRarSplits(File archiveFile) { - // Old-style RAR splits: main.rar + main.r01, main.r02, ... - String name = archiveFile.getName(); - if (!name.toLowerCase(Locale.ROOT).endsWith(".rar")) { - return false; - } - File parent = archiveFile.getParentFile(); - if (parent == null || !parent.exists()) { - return false; - } - File[] siblings = parent.listFiles(); - if (siblings == null) { - return false; - } - String stem = name.substring(0, name.length() - 4); - for (File sibling : siblings) { - if (!sibling.isFile()) { - continue; - } - String sibName = sibling.getName(); - if (sibName.length() > stem.length() + 1 && sibName.substring(0, stem.length()).equalsIgnoreCase(stem)) { - String suffix = sibName.substring(stem.length()); - if (RAR_OLDSPLIT_RE.matcher(suffix).matches() || suffix.toLowerCase(Locale.ROOT).matches("\\.r\\d{2,3}")) { - return true; - } - } - } - return false; - } - private static boolean isWrongPassword(ZipException error, boolean encrypted) { if (error == null) { return false; diff --git a/src/main/extractor.ts b/src/main/extractor.ts index ac8bbfa..ca10774 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -1169,38 +1169,63 @@ async function runExternalExtract( } logger.warn(`JVM-Extractor nicht verfügbar, nutze Legacy-Extractor: ${path.basename(archivePath)}`); } else { + if (hybridMode) { + try { + const archiveStat = await fs.promises.stat(archivePath); + logger.info(`Hybrid-Extract JVM: ${path.basename(archivePath)} (${(archiveStat.size / 1048576).toFixed(1)} MB)`); + } catch (statErr) { + logger.warn(`Hybrid-Extract JVM: Archiv nicht zugreifbar: ${path.basename(archivePath)} — ${String(statErr)}`); + } + } logger.info(`JVM-Extractor aktiv (${layout.rootDir}): ${path.basename(archivePath)}`); - const jvmResult = await runJvmExtractCommand( - layout, - archivePath, - targetDir, - conflictMode, - passwordCandidates, - onArchiveProgress, - signal, - timeoutMs - ); + const maxJvmAttempts = hybridMode ? 2 : 1; + for (let jvmAttempt = 1; jvmAttempt <= maxJvmAttempts; jvmAttempt++) { + const jvmResult = await runJvmExtractCommand( + layout, + archivePath, + targetDir, + conflictMode, + passwordCandidates, + onArchiveProgress, + signal, + timeoutMs + ); - if (jvmResult.ok) { - logger.info(`Entpackt via ${jvmResult.backend || "jvm"} [CPU=Idle, I/O=Normal, single-thread]: ${path.basename(archivePath)}`); - return jvmResult.usedPassword; - } - if (jvmResult.aborted) { - throw new Error("aborted:extract"); - } - if (jvmResult.timedOut) { - throw new Error(jvmResult.errorText || `Entpacken Timeout nach ${Math.ceil(timeoutMs / 1000)}s`); - } + if (jvmResult.ok) { + if (jvmAttempt > 1) { + logger.info(`JVM-Extractor Retry #${jvmAttempt - 1} erfolgreich: ${path.basename(archivePath)}`); + } + logger.info(`Entpackt via ${jvmResult.backend || "jvm"} [CPU=Idle, I/O=Normal, single-thread]: ${path.basename(archivePath)}`); + return jvmResult.usedPassword; + } + if (jvmResult.aborted) { + throw new Error("aborted:extract"); + } + if (jvmResult.timedOut) { + throw new Error(jvmResult.errorText || `Entpacken Timeout nach ${Math.ceil(timeoutMs / 1000)}s`); + } - jvmFailureReason = jvmResult.errorText || "JVM-Extractor fehlgeschlagen"; - const isUnsupportedMethod = jvmFailureReason.includes("UNSUPPORTEDMETHOD"); - if (backendMode === "jvm" && !isUnsupportedMethod) { - throw new Error(jvmFailureReason); - } - if (isUnsupportedMethod) { - logger.warn(`JVM-Extractor: Komprimierungsmethode nicht unterstützt, fallback auf Legacy: ${path.basename(archivePath)}`); - } else { - logger.warn(`JVM-Extractor Fehler, fallback auf Legacy: ${jvmFailureReason}`); + jvmFailureReason = jvmResult.errorText || "JVM-Extractor fehlgeschlagen"; + + // In hybrid mode, retry once on "codecs" / "can't be opened" errors — + // these can be caused by transient Windows file locks right after download completion. + const isTransientOpen = jvmFailureReason.includes("codecs") || jvmFailureReason.includes("can't be opened"); + if (hybridMode && isTransientOpen && jvmAttempt < maxJvmAttempts) { + logger.warn(`JVM-Extractor Hybrid-Retry: ${jvmFailureReason} — warte 3s vor Versuch #${jvmAttempt + 1}: ${path.basename(archivePath)}`); + await new Promise((r) => setTimeout(r, 3000)); + continue; + } + + const isUnsupportedMethod = jvmFailureReason.includes("UNSUPPORTEDMETHOD"); + if (backendMode === "jvm" && !isUnsupportedMethod) { + throw new Error(jvmFailureReason); + } + if (isUnsupportedMethod) { + logger.warn(`JVM-Extractor: Komprimierungsmethode nicht unterstützt, fallback auf Legacy: ${path.basename(archivePath)}`); + } else { + logger.warn(`JVM-Extractor Fehler, fallback auf Legacy: ${jvmFailureReason}`); + } + break; } } }