Fix absolute archive paths, show provider account number in UI
- extractor: detect UnRAR "Cannot create...\..." error (archive with leading-backslash internal paths) and retry in flat mode (-e) which strips all paths and avoids the invalid double-separator on Windows - types/download-manager: add providerLabel field to DownloadItem, store full label (e.g. "Debrid-Link #1") set at unrestrict time - App: display providerLabel in Service column (falls back to generic provider name if label not yet set) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3e2d70485c
commit
1cbda1350e
@ -5231,6 +5231,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
// Unrestrict succeeded - reset provider failure counter
|
// Unrestrict succeeded - reset provider failure counter
|
||||||
this.recordProviderSuccess(this.getProviderFailureKeyForItem(item, unrestricted.provider));
|
this.recordProviderSuccess(this.getProviderFailureKeyForItem(item, unrestricted.provider));
|
||||||
item.provider = unrestricted.provider;
|
item.provider = unrestricted.provider;
|
||||||
|
item.providerLabel = unrestricted.providerLabel;
|
||||||
item.retries += unrestricted.retriesUsed;
|
item.retries += unrestricted.retriesUsed;
|
||||||
item.fileName = sanitizeFilename(unrestricted.fileName || filenameFromUrl(item.url));
|
item.fileName = sanitizeFilename(unrestricted.fileName || filenameFromUrl(item.url));
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1637,11 +1637,15 @@ export function buildExternalExtractArgs(
|
|||||||
conflictMode: ConflictMode,
|
conflictMode: ConflictMode,
|
||||||
password = "",
|
password = "",
|
||||||
usePerformanceFlags = true,
|
usePerformanceFlags = true,
|
||||||
hybridMode = false
|
hybridMode = false,
|
||||||
|
flatMode = false
|
||||||
): string[] {
|
): string[] {
|
||||||
const mode = effectiveConflictMode(conflictMode);
|
const mode = effectiveConflictMode(conflictMode);
|
||||||
const lower = command.toLowerCase();
|
const lower = command.toLowerCase();
|
||||||
if (lower.includes("unrar") || lower.includes("winrar")) {
|
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-";
|
const overwrite = mode === "overwrite" ? "-o+" : mode === "rename" ? "-or" : "-o-";
|
||||||
// NOTE: The password is passed as a CLI argument (-p<password>), which means it may be
|
// NOTE: The password is passed as a CLI argument (-p<password>), which means it may be
|
||||||
// visible via process listing tools (e.g. `ps aux` on Unix). This is unavoidable because
|
// 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()
|
const perfArgs = usePerformanceFlags && shouldUseExtractorPerformanceFlags()
|
||||||
? ["-idc", extractorThreadSwitch(hybridMode, currentExtractCpuPriority)]
|
? ["-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";
|
const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos";
|
||||||
@ -1933,6 +1937,34 @@ async function runExternalExtractInner(
|
|||||||
lastError = result.errorText;
|
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");
|
throw new Error(lastError || "Entpacken fehlgeschlagen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4829,7 +4829,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
|
|||||||
return <span key={col} className="pkg-col pkg-col-hoster" title={hosterText}>{hosterText}</span>;
|
return <span key={col} className="pkg-col pkg-col-hoster" title={hosterText}>{hosterText}</span>;
|
||||||
}
|
}
|
||||||
case "account": {
|
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 <span key={col} className="pkg-col pkg-col-account" title={accountText}>{accountText}</span>;
|
return <span key={col} className="pkg-col pkg-col-account" title={accountText}>{accountText}</span>;
|
||||||
}
|
}
|
||||||
case "prio": return (
|
case "prio": return (
|
||||||
@ -4890,7 +4890,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case "hoster": { const h = extractHoster(item.url) || ""; return <span key={col} className="pkg-col pkg-col-hoster" title={h}>{h}</span>; }
|
case "hoster": { const h = extractHoster(item.url) || ""; return <span key={col} className="pkg-col pkg-col-hoster" title={h}>{h}</span>; }
|
||||||
case "account": return <span key={col} className="pkg-col pkg-col-account">{item.provider ? providerLabels[item.provider] : ""}</span>;
|
case "account": return <span key={col} className="pkg-col pkg-col-account">{item.providerLabel || (item.provider ? providerLabels[item.provider] : "")}</span>;
|
||||||
case "prio": return <span key={col} className="pkg-col pkg-col-prio"></span>;
|
case "prio": return <span key={col} className="pkg-col pkg-col-prio"></span>;
|
||||||
case "status": return (
|
case "status": return (
|
||||||
<span key={col} className="pkg-col pkg-col-status" title={item.retries > 0 ? `${item.fullStatus} · R${item.retries}` : item.fullStatus}>
|
<span key={col} className="pkg-col pkg-col-status" title={item.retries > 0 ? `${item.fullStatus} · R${item.retries}` : item.fullStatus}>
|
||||||
|
|||||||
@ -118,6 +118,7 @@ export interface DownloadItem {
|
|||||||
packageId: string;
|
packageId: string;
|
||||||
url: string;
|
url: string;
|
||||||
provider: DebridProvider | null;
|
provider: DebridProvider | null;
|
||||||
|
providerLabel?: string;
|
||||||
status: DownloadStatus;
|
status: DownloadStatus;
|
||||||
retries: number;
|
retries: number;
|
||||||
speedBps: number;
|
speedBps: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user