Release v1.6.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1d4a13466f
commit
693f7b482a
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.3",
|
||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -286,6 +286,9 @@ export class AppController {
|
||||
this.settings = restoredSettings;
|
||||
saveSettings(this.storagePaths, this.settings);
|
||||
this.manager.setSettings(this.settings);
|
||||
// Stop the manager BEFORE saving the restored session to prevent
|
||||
// the auto-save timer from overwriting it with the old in-memory session.
|
||||
this.manager.stop();
|
||||
const restoredSession = parsed.session as ReturnType<typeof loadSession>;
|
||||
saveSession(this.storagePaths, restoredSession);
|
||||
return { restored: true, message: "Backup wiederhergestellt. Bitte App neustarten." };
|
||||
|
||||
@ -2576,8 +2576,6 @@ export class DownloadManager extends EventEmitter {
|
||||
const p = this.session.packages[order[i]];
|
||||
if (p && p.priority === "high") {
|
||||
insertAt = i + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
order.splice(insertAt, 0, packageId);
|
||||
@ -2890,6 +2888,7 @@ export class DownloadManager extends EventEmitter {
|
||||
// duration, average speed, and ETA are calculated relative to the current run, not cumulative.
|
||||
this.session.runStartedAt = nowMs();
|
||||
this.session.totalDownloadedBytes = 0;
|
||||
this.sessionDownloadedBytes = 0;
|
||||
this.session.summaryText = "";
|
||||
this.session.reconnectUntil = 0;
|
||||
this.session.reconnectReason = "";
|
||||
@ -3110,8 +3109,8 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
// Clear stale transient status texts from previous session
|
||||
if (item.status === "queued") {
|
||||
const fs = (item.fullStatus || "").trim();
|
||||
if (fs !== "Wartet" && fs !== "Paket gestoppt" && fs !== "Online") {
|
||||
const statusText = (item.fullStatus || "").trim();
|
||||
if (statusText !== "Wartet" && statusText !== "Paket gestoppt" && statusText !== "Online") {
|
||||
item.fullStatus = "Wartet";
|
||||
}
|
||||
}
|
||||
@ -3120,8 +3119,8 @@ export class DownloadManager extends EventEmitter {
|
||||
item.onlineStatus = undefined;
|
||||
}
|
||||
if (item.status === "completed") {
|
||||
const fs = (item.fullStatus || "").trim();
|
||||
if (fs && !isExtractedLabel(fs) && !/^Fertig\b/i.test(fs)) {
|
||||
const statusText = (item.fullStatus || "").trim();
|
||||
if (statusText && !isExtractedLabel(statusText) && !/^Fertig\b/i.test(statusText)) {
|
||||
item.fullStatus = `Fertig (${humanSize(item.downloadedBytes)})`;
|
||||
}
|
||||
}
|
||||
@ -3165,7 +3164,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (failed > 0) {
|
||||
pkg.status = "failed";
|
||||
} else if (cancelled > 0) {
|
||||
pkg.status = success > 0 ? "failed" : "cancelled";
|
||||
pkg.status = success > 0 ? "completed" : "cancelled";
|
||||
} else if (success > 0) {
|
||||
pkg.status = "completed";
|
||||
}
|
||||
@ -4927,9 +4926,12 @@ export class DownloadManager extends EventEmitter {
|
||||
if (writeMode === "w" && item.totalBytes && item.totalBytes > 0 && process.platform === "win32") {
|
||||
try {
|
||||
const fd = await fs.promises.open(effectiveTargetPath, "w");
|
||||
try {
|
||||
await fd.truncate(item.totalBytes);
|
||||
await fd.close();
|
||||
preAllocated = true;
|
||||
} finally {
|
||||
await fd.close();
|
||||
}
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
@ -5281,6 +5283,11 @@ export class DownloadManager extends EventEmitter {
|
||||
if (!stream.destroyed) {
|
||||
stream.destroy();
|
||||
}
|
||||
// If the body read succeeded but the final flush or stream close failed,
|
||||
// propagate the error so the download is retried instead of marked complete.
|
||||
if (bodyError) {
|
||||
throw bodyError;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect tiny error-response files (e.g. hoster returning "Forbidden" with HTTP 200).
|
||||
@ -6330,7 +6337,7 @@ export class DownloadManager extends EventEmitter {
|
||||
} else if (failed > 0) {
|
||||
pkg.status = "failed";
|
||||
} else if (cancelled > 0) {
|
||||
pkg.status = success > 0 ? "failed" : "cancelled";
|
||||
pkg.status = success > 0 ? "completed" : "cancelled";
|
||||
} else {
|
||||
pkg.status = "completed";
|
||||
}
|
||||
@ -6504,21 +6511,19 @@ export class DownloadManager extends EventEmitter {
|
||||
if (event) {
|
||||
bandwidthSamples.push({
|
||||
timestamp: event.at,
|
||||
speedBps: event.bytes * 3
|
||||
speedBps: Math.floor(event.bytes * (1000 / 120))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const paused = this.session.running && this.session.paused;
|
||||
const currentSpeedBps = paused ? 0 : this.speedBytesLastWindow / 3;
|
||||
const currentSpeedBps = paused ? 0 : this.speedBytesLastWindow / SPEED_WINDOW_SECONDS;
|
||||
|
||||
let totalBytes = 0;
|
||||
let maxSpeed = 0;
|
||||
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
||||
const event = this.speedEvents[i];
|
||||
if (event) {
|
||||
totalBytes += event.bytes;
|
||||
const speed = event.bytes * 3;
|
||||
const speed = Math.floor(event.bytes * (1000 / 120));
|
||||
if (speed > maxSpeed) {
|
||||
maxSpeed = speed;
|
||||
}
|
||||
|
||||
@ -233,9 +233,9 @@ const BandwidthChart = memo(function BandwidthChart({ items, running, paused, sp
|
||||
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillText("0s", padding.left, height - padding.bottom + 8);
|
||||
ctx.fillText("60s", padding.left, height - padding.bottom + 8);
|
||||
ctx.fillText("30s", padding.left + chartWidth / 2, height - padding.bottom + 8);
|
||||
ctx.fillText("60s", width - padding.right, height - padding.bottom + 8);
|
||||
ctx.fillText("0s", width - padding.right, height - padding.bottom + 8);
|
||||
|
||||
if (history.length < 2) {
|
||||
ctx.fillStyle = textColor;
|
||||
@ -2320,9 +2320,9 @@ export function App(): ReactElement {
|
||||
</span>
|
||||
{selectedHistoryIds.size > 0 && (
|
||||
<button className="btn btn-danger" onClick={() => {
|
||||
const ids = [...selectedHistoryIds];
|
||||
void Promise.all(ids.map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
||||
setHistoryEntries((prev) => prev.filter((e) => !selectedHistoryIds.has(e.id)));
|
||||
const idSet = new Set(selectedHistoryIds);
|
||||
void Promise.all([...idSet].map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
||||
setHistoryEntries((prev) => prev.filter((e) => !idSet.has(e.id)));
|
||||
setSelectedHistoryIds(new Set());
|
||||
});
|
||||
}}>Ausgewählte entfernen ({selectedHistoryIds.size})</button>
|
||||
@ -3091,7 +3091,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
|
||||
const failed = items.filter((item) => item.status === "failed").length;
|
||||
const cancelled = items.filter((item) => item.status === "cancelled").length;
|
||||
const extracted = items.filter((item) => item.fullStatus?.startsWith("Entpackt")).length;
|
||||
const extracting = items.some((item) => item.fullStatus?.startsWith("Entpack"));
|
||||
const extracting = items.some((item) => item.fullStatus?.startsWith("Entpacken"));
|
||||
const total = Math.max(1, items.length);
|
||||
// Use 50/50 split when extraction is active OR package is in extracting state
|
||||
// (prevents bar jumping from 100% to 50% when extraction starts)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user