Round 8 bug fixes (20 fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-04 22:14:03 +01:00
parent 75775f2798
commit 74920e2e2f
5 changed files with 30 additions and 17 deletions

View File

@ -640,7 +640,7 @@ class MegaDebridClient {
throw new Error("Mega-Web Antwort ohne Download-Link"); throw new Error("Mega-Web Antwort ohne Download-Link");
} }
if (!lastError) { if (!lastError) {
lastError = web ? "Mega-Web Antwort ohne Download-Link" : "Mega-Web Antwort leer"; lastError = "Mega-Web Antwort leer";
} }
// Don't retry permanent hoster errors (dead link, file removed, etc.) // Don't retry permanent hoster errors (dead link, file removed, etc.)
if (/permanent ungültig|hosternotavailable|file.?not.?found|file.?unavailable|link.?is.?dead/i.test(lastError)) { if (/permanent ungültig|hosternotavailable|file.?not.?found|file.?unavailable|link.?is.?dead/i.test(lastError)) {

View File

@ -1284,8 +1284,6 @@ export class DownloadManager extends EventEmitter {
this.packagePostProcessWaiters = []; this.packagePostProcessWaiters = [];
this.summary = null; this.summary = null;
this.nonResumableActive = 0; this.nonResumableActive = 0;
this.retryAfterByItem.clear();
this.retryStateByItem.clear();
this.resetSessionTotalsIfQueueEmpty(); this.resetSessionTotalsIfQueueEmpty();
this.persistNow(); this.persistNow();
this.emitState(true); this.emitState(true);
@ -1540,10 +1538,11 @@ export class DownloadManager extends EventEmitter {
item.attempts = 0; item.attempts = 0;
item.lastError = ""; item.lastError = "";
item.fullStatus = "Wartet"; item.fullStatus = "Wartet";
item.provider = null;
item.updatedAt = nowMs(); item.updatedAt = nowMs();
this.assignItemTargetPath(item, path.join(pkg.outputDir, sanitizeFilename(item.fileName || filenameFromUrl(item.url)))); this.assignItemTargetPath(item, path.join(pkg.outputDir, sanitizeFilename(item.fileName || filenameFromUrl(item.url))));
this.runOutcomes.delete(itemId); this.runOutcomes.delete(itemId);
this.itemContributedBytes.delete(itemId); this.dropItemContribution(itemId);
this.retryAfterByItem.delete(itemId); this.retryAfterByItem.delete(itemId);
this.retryStateByItem.delete(itemId); this.retryStateByItem.delete(itemId);
if (this.session.running) { if (this.session.running) {
@ -2823,6 +2822,7 @@ export class DownloadManager extends EventEmitter {
} }
// Not running: start with only items from specified packages // Not running: start with only items from specified packages
this.triggerPendingExtractions();
const runItems = Object.values(this.session.items) const runItems = Object.values(this.session.items)
.filter((item) => { .filter((item) => {
if (!targetSet.has(item.packageId)) return false; if (!targetSet.has(item.packageId)) return false;
@ -2928,6 +2928,7 @@ export class DownloadManager extends EventEmitter {
} }
// Not running: start with only specified items // Not running: start with only specified items
this.triggerPendingExtractions();
const runItems = [...targetSet] const runItems = [...targetSet]
.map((id) => this.session.items[id]) .map((id) => this.session.items[id])
.filter((item) => { .filter((item) => {
@ -3049,6 +3050,7 @@ export class DownloadManager extends EventEmitter {
this.runOutcomes.clear(); this.runOutcomes.clear();
this.runCompletedPackages.clear(); this.runCompletedPackages.clear();
this.retryAfterByItem.clear(); this.retryAfterByItem.clear();
this.retryStateByItem.clear();
this.reservedTargetPaths.clear(); this.reservedTargetPaths.clear();
this.claimedTargetPathByItem.clear(); this.claimedTargetPathByItem.clear();
this.session.running = false; this.session.running = false;
@ -3323,7 +3325,8 @@ export class DownloadManager extends EventEmitter {
|| item.status === "paused" || item.status === "paused"
|| item.status === "reconnect_wait") { || item.status === "reconnect_wait") {
item.status = "queued"; item.status = "queued";
item.fullStatus = "Wartet"; const itemPkg = this.session.packages[item.packageId];
item.fullStatus = (itemPkg && itemPkg.enabled === false) ? "Paket gestoppt" : "Wartet";
item.speedBps = 0; item.speedBps = 0;
item.updatedAt = nowMs(); item.updatedAt = nowMs();
} }
@ -3837,7 +3840,7 @@ export class DownloadManager extends EventEmitter {
let changed = false; let changed = false;
for (const packageId of packageIds) { for (const packageId of packageIds) {
const pkg = this.session.packages[packageId]; const pkg = this.session.packages[packageId];
if (!pkg || pkg.cancelled) { if (!pkg || pkg.cancelled || !pkg.enabled) {
continue; continue;
} }
@ -5714,6 +5717,7 @@ export class DownloadManager extends EventEmitter {
try { try {
await fs.promises.rm(effectiveTargetPath, { force: true }); await fs.promises.rm(effectiveTargetPath, { force: true });
} catch { /* ignore */ } } catch { /* ignore */ }
this.releaseTargetPath(active.itemId);
this.dropItemContribution(active.itemId); this.dropItemContribution(active.itemId);
item.downloadedBytes = 0; item.downloadedBytes = 0;
item.progressPercent = 0; item.progressPercent = 0;
@ -6396,7 +6400,7 @@ export class DownloadManager extends EventEmitter {
} }
const updatedAt = nowMs(); const updatedAt = nowMs();
for (const entry of archItems) { for (const entry of archItems) {
if (!isExtractedLabel(entry.fullStatus)) { if (!isExtractedLabel(entry.fullStatus) && entry.fullStatus !== label) {
entry.fullStatus = label; entry.fullStatus = label;
entry.updatedAt = updatedAt; entry.updatedAt = updatedAt;
} }
@ -6536,7 +6540,7 @@ export class DownloadManager extends EventEmitter {
if (!allDone && this.settings.hybridExtract && this.settings.autoExtract && failed === 0 && success > 0) { if (!allDone && this.settings.hybridExtract && this.settings.autoExtract && failed === 0 && success > 0) {
await this.runHybridExtraction(packageId, pkg, items, signal); await this.runHybridExtraction(packageId, pkg, items, signal);
if (signal?.aborted) { if (signal?.aborted) {
pkg.status = (pkg.enabled && !this.session.paused) ? "queued" : "paused"; pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "queued" : "paused";
pkg.updatedAt = nowMs(); pkg.updatedAt = nowMs();
return; return;
} }
@ -6764,8 +6768,8 @@ export class DownloadManager extends EventEmitter {
for (const entry of completedItems) { for (const entry of completedItems) {
if (!isExtractedLabel(entry.fullStatus)) { if (!isExtractedLabel(entry.fullStatus)) {
entry.fullStatus = `Entpack-Fehler: ${timeoutReason}`; entry.fullStatus = `Entpack-Fehler: ${timeoutReason}`;
entry.updatedAt = nowMs();
} }
entry.updatedAt = nowMs();
} }
pkg.status = "failed"; pkg.status = "failed";
pkg.updatedAt = nowMs(); pkg.updatedAt = nowMs();

View File

@ -2122,7 +2122,13 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
if (passwordCandidates.length > 1 && pendingCandidates.length > 1) { if (passwordCandidates.length > 1 && pendingCandidates.length > 1) {
logger.info(`Passwort-Discovery: Extrahiere erstes Archiv seriell (${passwordCandidates.length} Passwort-Kandidaten)...`); logger.info(`Passwort-Discovery: Extrahiere erstes Archiv seriell (${passwordCandidates.length} Passwort-Kandidaten)...`);
const first = pendingCandidates[0]; const first = pendingCandidates[0];
await extractSingleArchive(first); try {
await extractSingleArchive(first);
} catch (err) {
const errText = String(err);
if (/aborted:extract/i.test(errText)) throw err;
// noextractor:skipped — handled by noExtractorEncountered flag below
}
parallelQueue = pendingCandidates.slice(1); parallelQueue = pendingCandidates.slice(1);
if (parallelQueue.length > 0) { if (parallelQueue.length > 0) {
logger.info(`Passwort-Discovery abgeschlossen, starte parallele Extraktion für ${parallelQueue.length} verbleibende Archive`); logger.info(`Passwort-Discovery abgeschlossen, starte parallele Extraktion für ${parallelQueue.length} verbleibende Archive`);

View File

@ -485,6 +485,7 @@ async function writeSettingsPayload(paths: StoragePaths, payload: string): Promi
await fsp.copyFile(tempPath, paths.configFile); await fsp.copyFile(tempPath, paths.configFile);
await fsp.rm(tempPath, { force: true }).catch(() => {}); await fsp.rm(tempPath, { force: true }).catch(() => {});
} else { } else {
await fsp.rm(tempPath, { force: true }).catch(() => {});
throw renameError; throw renameError;
} }
} }
@ -605,6 +606,7 @@ async function writeSessionPayload(paths: StoragePaths, payload: string, generat
await fsp.copyFile(tempPath, paths.sessionFile); await fsp.copyFile(tempPath, paths.sessionFile);
await fsp.rm(tempPath, { force: true }).catch(() => {}); await fsp.rm(tempPath, { force: true }).catch(() => {});
} else { } else {
await fsp.rm(tempPath, { force: true }).catch(() => {});
throw renameError; throw renameError;
} }
} }

View File

@ -316,6 +316,7 @@ const BandwidthChart = memo(function BandwidthChart({ items, running, paused, sp
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = requestAnimationFrame(drawChart); animationFrameRef.current = requestAnimationFrame(drawChart);
}; };
@ -907,7 +908,7 @@ export function App(): ReactElement {
return next; return next;
}); });
} }
}, [packages, snapshot.session.items]); }, [packages, snapshot.session.items, collapsedPackages]);
const allPackagesCollapsed = useMemo(() => ( const allPackagesCollapsed = useMemo(() => (
packages.length > 0 && packages.every((pkg) => collapsedPackages[pkg.id]) packages.length > 0 && packages.every((pkg) => collapsedPackages[pkg.id])
@ -1855,7 +1856,7 @@ export function App(): ReactElement {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") { if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") {
// Don't clear selection if an overlay is open — let the overlay close first // Don't clear selection if an overlay is open — let the overlay close first
if (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop") || document.querySelector(".link-popup-overlay")) return; if (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop")) return;
if (tabRef.current === "downloads") setSelectedIds(new Set()); if (tabRef.current === "downloads") setSelectedIds(new Set());
else if (tabRef.current === "history") setSelectedHistoryIds(new Set()); else if (tabRef.current === "history") setSelectedHistoryIds(new Set());
} }
@ -2869,7 +2870,7 @@ export function App(): ReactElement {
const pkg = snapshot.session.packages[id]; const pkg = snapshot.session.packages[id];
if (pkg) { for (const iid of pkg.itemIds) removedItemIds.add(iid); } if (pkg) { for (const iid of pkg.itemIds) removedItemIds.add(iid); }
} }
const totalRemaining = Object.keys(snapshot.session.items).length - removedItemIds.size; const totalRemaining = Math.max(0, Object.keys(snapshot.session.items).length - removedItemIds.size);
const parts: string[] = []; const parts: string[] = [];
if (pkgCount > 0) parts.push(`${pkgCount} Paket(e)`); if (pkgCount > 0) parts.push(`${pkgCount} Paket(e)`);
if (itemCount > 0) parts.push(`${itemCount} Link(s)`); if (itemCount > 0) parts.push(`${itemCount} Link(s)`);
@ -3257,7 +3258,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
} }
return sum; return sum;
}, 0); }, 0);
const dlProgress = Math.floor(((done + activeProgress) / total) * (useExtractSplit ? 50 : 100)); const dlProgress = Math.min(useExtractSplit ? 50 : 100, Math.floor(((done + activeProgress) / total) * (useExtractSplit ? 50 : 100)));
// Include fractional progress from items currently being extracted // Include fractional progress from items currently being extracted
const extractingProgress = items.reduce((sum, item) => { const extractingProgress = items.reduce((sum, item) => {
const fs = item.fullStatus || ""; const fs = item.fullStatus || "";
@ -3266,8 +3267,8 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
if (m) return sum + Number(m[1]) / 100; if (m) return sum + Number(m[1]) / 100;
return sum; return sum;
}, 0); }, 0);
const exProgress = Math.floor(((extracted + extractingProgress) / total) * 50); const exProgress = Math.min(50, Math.floor(((extracted + extractingProgress) / total) * 50));
const combinedProgress = useExtractSplit ? dlProgress + exProgress : dlProgress; const combinedProgress = Math.min(100, useExtractSplit ? dlProgress + exProgress : dlProgress);
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => { const onKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
if (e.key === "Enter") { onFinishEdit(pkg.id, pkg.name, editingName); } if (e.key === "Enter") { onFinishEdit(pkg.id, pkg.name, editingName); }
@ -3396,7 +3397,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
) : ""} ) : ""}
</span> </span>
); );
case "hoster": return <span key={col} className="pkg-col pkg-col-hoster" title={extractHoster(item.url)}>{extractHoster(item.url) || ""}</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.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 (