diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 5764f0d..e4b87cb 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -5231,6 +5231,7 @@ export class DownloadManager extends EventEmitter { // Unrestrict succeeded - reset provider failure counter this.recordProviderSuccess(this.getProviderFailureKeyForItem(item, unrestricted.provider)); item.provider = unrestricted.provider; + item.providerLabel = unrestricted.providerLabel; item.retries += unrestricted.retriesUsed; item.fileName = sanitizeFilename(unrestricted.fileName || filenameFromUrl(item.url)); try { diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 00951e9..0fa9ba5 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -1637,11 +1637,15 @@ export function buildExternalExtractArgs( conflictMode: ConflictMode, password = "", usePerformanceFlags = true, - hybridMode = false + hybridMode = false, + flatMode = false ): string[] { const mode = effectiveConflictMode(conflictMode); const lower = command.toLowerCase(); if (lower.includes("unrar") || lower.includes("winrar")) { + // "e" extracts without paths (flat). Used as fallback when the archive stores paths with a + // leading \ (absolute-style), which causes UnRAR to produce invalid \\ double-separators. + const extractCmd = flatMode ? "e" : "x"; const overwrite = mode === "overwrite" ? "-o+" : mode === "rename" ? "-or" : "-o-"; // NOTE: The password is passed as a CLI argument (-p), which means it may be // visible via process listing tools (e.g. `ps aux` on Unix). This is unavoidable because @@ -1651,7 +1655,7 @@ export function buildExternalExtractArgs( const perfArgs = usePerformanceFlags && shouldUseExtractorPerformanceFlags() ? ["-idc", extractorThreadSwitch(hybridMode, currentExtractCpuPriority)] : []; - return ["x", overwrite, pass, "-y", ...perfArgs, archivePath, `${targetDir}${path.sep}`]; + return [extractCmd, overwrite, pass, "-y", ...perfArgs, archivePath, `${targetDir}${path.sep}`]; } const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos"; @@ -1933,6 +1937,34 @@ async function runExternalExtractInner( lastError = result.errorText; } + // Some archives (e.g. created by certain scene groups) store internal paths with a leading \, + // causing UnRAR to construct invalid \\ double-separator paths on Windows. Retry in flat mode + // ("e" instead of "x") which strips all archive paths and extracts files directly to targetDir. + const isAbsoluteArchivePath = lastError.includes("Cannot create") && lastError.includes("\\\\"); + if (isAbsoluteArchivePath) { + logger.warn(`Entpack-Pfadfehler: absoluter Archivpfad erkannt, Wiederholung mit flachem Modus: ${path.basename(archivePath)}`); + bestPercent = 0; + passwordAttempt = 0; + for (const password of passwords) { + if (signal?.aborted) throw new Error("aborted:extract"); + passwordAttempt += 1; + const quotedPw = password === "" ? '""' : `"${password}"`; + logger.info(`Flach-Extraktion Versuch ${passwordAttempt}/${passwords.length} für ${path.basename(archivePath)}: ${quotedPw}`); + const args = buildExternalExtractArgs(command, archivePath, targetDir, conflictMode, password, usePerformanceFlags, hybridMode, true); + const result = await runExtractCommand(command, args, (chunk) => { + const parsed = parseProgressPercent(chunk); + if (parsed === null) return; + const next = nextArchivePercent(bestPercent, parsed); + if (next !== bestPercent) { bestPercent = next; onArchiveProgress?.(bestPercent); } + }, signal, timeoutMs); + logger.info(`Flach-Extraktion Versuch ${passwordAttempt}/${passwords.length}: ok=${result.ok}, bestPercent=${bestPercent}`); + if (result.ok) { onArchiveProgress?.(100); return password; } + if (result.aborted) throw new Error("aborted:extract"); + if (result.timedOut || result.missingCommand) break; + lastError = result.errorText; + } + } + throw new Error(lastError || "Entpacken fehlgeschlagen"); } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 63237e1..73c62da 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -4829,7 +4829,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs return {hosterText}; } case "account": { - const accountText = [...new Set(items.map((item) => item.provider).filter(Boolean))].map((p) => providerLabels[p!] || p).join(", "); + const accountText = [...new Set(items.map((item) => item.providerLabel || (item.provider ? providerLabels[item.provider] : null)).filter(Boolean))].join(", "); return {accountText}; } case "prio": return ( @@ -4890,7 +4890,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs ); case "hoster": { const h = extractHoster(item.url) || ""; return {h}; } - case "account": return {item.provider ? providerLabels[item.provider] : ""}; + case "account": return {item.providerLabel || (item.provider ? providerLabels[item.provider] : "")}; case "prio": return ; case "status": return ( 0 ? `${item.fullStatus} · R${item.retries}` : item.fullStatus}> diff --git a/src/shared/types.ts b/src/shared/types.ts index ce27dfe..a5a2a1d 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -118,6 +118,7 @@ export interface DownloadItem { packageId: string; url: string; provider: DebridProvider | null; + providerLabel?: string; status: DownloadStatus; retries: number; speedBps: number;