diff --git a/src/main/debrid.ts b/src/main/debrid.ts
index d011485..04e40da 100644
--- a/src/main/debrid.ts
+++ b/src/main/debrid.ts
@@ -650,7 +650,7 @@ class MegaDebridClient {
await sleepWithSignal(retryDelay(attempt), signal);
}
}
- throw new Error(lastError || "Mega-Web Unrestrict fehlgeschlagen");
+ throw new Error(String(lastError || "Mega-Web Unrestrict fehlgeschlagen").replace(/^Error:\s*/i, ""));
}
}
@@ -954,7 +954,7 @@ class AllDebridClient {
}
}
- throw new Error(lastError || "AllDebrid Unrestrict fehlgeschlagen");
+ throw new Error(String(lastError || "AllDebrid Unrestrict fehlgeschlagen").replace(/^Error:\s*/i, ""));
}
}
diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts
index f6dae52..7b1b1e0 100644
--- a/src/main/download-manager.ts
+++ b/src/main/download-manager.ts
@@ -1102,16 +1102,21 @@ export class DownloadManager extends EventEmitter {
active.abortController.abort("cancel");
}
const pkg = this.session.packages[item.packageId];
+ let removedByPackageCleanup = false;
if (pkg) {
pkg.itemIds = pkg.itemIds.filter((id) => id !== itemId);
if (pkg.itemIds.length === 0) {
this.removePackageFromSession(item.packageId, [itemId]);
+ removedByPackageCleanup = true;
} else {
pkg.updatedAt = nowMs();
}
}
- delete this.session.items[itemId];
- this.itemCount = Math.max(0, this.itemCount - 1);
+ // removePackageFromSession already deletes the item and decrements itemCount
+ if (!removedByPackageCleanup) {
+ delete this.session.items[itemId];
+ this.itemCount = Math.max(0, this.itemCount - 1);
+ }
this.retryAfterByItem.delete(itemId);
this.retryStateByItem.delete(itemId);
this.dropItemContribution(itemId);
@@ -1268,6 +1273,7 @@ export class DownloadManager extends EventEmitter {
this.packagePostProcessTasks.clear();
this.packagePostProcessAbortControllers.clear();
this.hybridExtractRequeue.clear();
+ this.providerFailures.clear();
this.packagePostProcessQueue = Promise.resolve();
this.packagePostProcessActive = 0;
for (const waiter of this.packagePostProcessWaiters) { waiter.resolve(); }
@@ -1885,7 +1891,7 @@ export class DownloadManager extends EventEmitter {
}
for (const entry of entries) {
- if (entry.isFile()) {
+ if (entry.isFile() && !isIgnorableEmptyDirFileName(entry.name)) {
return true;
}
if (entry.isDirectory()) {
@@ -2739,6 +2745,7 @@ export class DownloadManager extends EventEmitter {
item.updatedAt = nowMs();
this.retryAfterByItem.delete(itemId);
this.retryStateByItem.delete(itemId);
+ this.releaseTargetPath(itemId);
this.recordRunOutcome(itemId, "cancelled");
affectedPackageIds.add(item.packageId);
}
@@ -2746,15 +2753,16 @@ export class DownloadManager extends EventEmitter {
const pkg = this.session.packages[pkgId];
if (pkg) this.refreshPackageStatus(pkg);
}
- // Trigger extraction if all items are now in a terminal state and some completed
+ // Trigger extraction if all items are now in a terminal state and some completed (no failures)
if (this.settings.autoExtract) {
for (const pkgId of affectedPackageIds) {
const pkg = this.session.packages[pkgId];
if (!pkg || pkg.cancelled || this.packagePostProcessTasks.has(pkgId)) continue;
const pkgItems = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[];
const hasPending = pkgItems.some((i) => i.status !== "completed" && i.status !== "failed" && i.status !== "cancelled");
+ const hasFailed = pkgItems.some((i) => i.status === "failed");
const hasUnextracted = pkgItems.some((i) => i.status === "completed" && !isExtractedLabel(i.fullStatus || ""));
- if (!hasPending && hasUnextracted) {
+ if (!hasPending && !hasFailed && hasUnextracted) {
for (const it of pkgItems) {
if (it.status === "completed" && !isExtractedLabel(it.fullStatus || "")) {
it.fullStatus = "Entpacken - Ausstehend";
@@ -2935,6 +2943,9 @@ export class DownloadManager extends EventEmitter {
this.runCompletedPackages.clear();
this.retryAfterByItem.clear();
this.retryStateByItem.clear();
+ this.itemContributedBytes.clear();
+ this.reservedTargetPaths.clear();
+ this.claimedTargetPathByItem.clear();
this.session.running = true;
this.session.paused = false;
this.session.runStartedAt = nowMs();
diff --git a/src/main/extractor.ts b/src/main/extractor.ts
index d0888df..ffbc292 100644
--- a/src/main/extractor.ts
+++ b/src/main/extractor.ts
@@ -1597,7 +1597,8 @@ async function extractZipArchive(archivePath: string, targetDir: string, conflic
const limitMb = Math.ceil(memoryLimitBytes / (1024 * 1024));
throw new Error(`ZIP-Eintrag zu groß für internen Entpacker (${entryMb} MB > ${limitMb} MB)`);
}
- if (data.length > Math.max(uncompressedSize, compressedSize) * 20) {
+ const maxDeclaredSize = Math.max(uncompressedSize, compressedSize);
+ if (maxDeclaredSize > 0 && data.length > maxDeclaredSize * 20) {
throw new Error(`ZIP-Eintrag verdächtig groß nach Entpacken (${entry.entryName})`);
}
await fs.promises.writeFile(outputPath, data);
diff --git a/src/main/realdebrid.ts b/src/main/realdebrid.ts
index 6a4d9cc..4fcd099 100644
--- a/src/main/realdebrid.ts
+++ b/src/main/realdebrid.ts
@@ -196,6 +196,6 @@ export class RealDebridClient {
}
}
- throw new Error(lastError || "Unrestrict fehlgeschlagen");
+ throw new Error(String(lastError || "Unrestrict fehlgeschlagen").replace(/^Error:\s*/i, ""));
}
}
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 4b86c37..4aa2a7e 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -1369,10 +1369,18 @@ export function App(): ReactElement {
pendingPackageOrderRef.current = [...order];
pendingPackageOrderAtRef.current = Date.now();
packageOrderRef.current = [...order];
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: [...order] } };
+ });
void window.rd.reorderPackages(order).catch((error) => {
pendingPackageOrderRef.current = null;
pendingPackageOrderAtRef.current = 0;
packageOrderRef.current = serverPackageOrderRef.current;
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: serverPackageOrderRef.current } };
+ });
showToast(`Sortierung fehlgeschlagen: ${String(error)}`, 2400);
});
}, [showToast]);
@@ -1389,10 +1397,18 @@ export function App(): ReactElement {
pendingPackageOrderRef.current = [...nextOrder];
pendingPackageOrderAtRef.current = Date.now();
packageOrderRef.current = [...nextOrder];
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: [...nextOrder] } };
+ });
void window.rd.reorderPackages(nextOrder).catch((error) => {
pendingPackageOrderRef.current = null;
pendingPackageOrderAtRef.current = 0;
packageOrderRef.current = serverPackageOrderRef.current;
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: serverPackageOrderRef.current } };
+ });
showToast(`Sortierung fehlgeschlagen: ${String(error)}`, 2400);
});
}, [showToast]);
@@ -2375,10 +2391,18 @@ export function App(): ReactElement {
pendingPackageOrderRef.current = [...sorted];
pendingPackageOrderAtRef.current = Date.now();
packageOrderRef.current = sorted;
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: [...sorted] } };
+ });
void window.rd.reorderPackages(sorted).catch((error) => {
pendingPackageOrderRef.current = null;
pendingPackageOrderAtRef.current = 0;
packageOrderRef.current = serverPackageOrderRef.current;
+ setSnapshot((prev) => {
+ if (!prev) return prev;
+ return { ...prev, session: { ...prev.session, packageOrder: serverPackageOrderRef.current } };
+ });
showToast(`Sortierung fehlgeschlagen: ${String(error)}`, 2400);
});
} : undefined}
@@ -2863,7 +2887,7 @@ export function App(): ReactElement {