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",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.2",
|
"version": "1.6.3",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -286,6 +286,9 @@ export class AppController {
|
|||||||
this.settings = restoredSettings;
|
this.settings = restoredSettings;
|
||||||
saveSettings(this.storagePaths, this.settings);
|
saveSettings(this.storagePaths, this.settings);
|
||||||
this.manager.setSettings(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>;
|
const restoredSession = parsed.session as ReturnType<typeof loadSession>;
|
||||||
saveSession(this.storagePaths, restoredSession);
|
saveSession(this.storagePaths, restoredSession);
|
||||||
return { restored: true, message: "Backup wiederhergestellt. Bitte App neustarten." };
|
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]];
|
const p = this.session.packages[order[i]];
|
||||||
if (p && p.priority === "high") {
|
if (p && p.priority === "high") {
|
||||||
insertAt = i + 1;
|
insertAt = i + 1;
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
order.splice(insertAt, 0, packageId);
|
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.
|
// duration, average speed, and ETA are calculated relative to the current run, not cumulative.
|
||||||
this.session.runStartedAt = nowMs();
|
this.session.runStartedAt = nowMs();
|
||||||
this.session.totalDownloadedBytes = 0;
|
this.session.totalDownloadedBytes = 0;
|
||||||
|
this.sessionDownloadedBytes = 0;
|
||||||
this.session.summaryText = "";
|
this.session.summaryText = "";
|
||||||
this.session.reconnectUntil = 0;
|
this.session.reconnectUntil = 0;
|
||||||
this.session.reconnectReason = "";
|
this.session.reconnectReason = "";
|
||||||
@ -3110,8 +3109,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
// Clear stale transient status texts from previous session
|
// Clear stale transient status texts from previous session
|
||||||
if (item.status === "queued") {
|
if (item.status === "queued") {
|
||||||
const fs = (item.fullStatus || "").trim();
|
const statusText = (item.fullStatus || "").trim();
|
||||||
if (fs !== "Wartet" && fs !== "Paket gestoppt" && fs !== "Online") {
|
if (statusText !== "Wartet" && statusText !== "Paket gestoppt" && statusText !== "Online") {
|
||||||
item.fullStatus = "Wartet";
|
item.fullStatus = "Wartet";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3120,8 +3119,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
item.onlineStatus = undefined;
|
item.onlineStatus = undefined;
|
||||||
}
|
}
|
||||||
if (item.status === "completed") {
|
if (item.status === "completed") {
|
||||||
const fs = (item.fullStatus || "").trim();
|
const statusText = (item.fullStatus || "").trim();
|
||||||
if (fs && !isExtractedLabel(fs) && !/^Fertig\b/i.test(fs)) {
|
if (statusText && !isExtractedLabel(statusText) && !/^Fertig\b/i.test(statusText)) {
|
||||||
item.fullStatus = `Fertig (${humanSize(item.downloadedBytes)})`;
|
item.fullStatus = `Fertig (${humanSize(item.downloadedBytes)})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3165,7 +3164,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (failed > 0) {
|
if (failed > 0) {
|
||||||
pkg.status = "failed";
|
pkg.status = "failed";
|
||||||
} else if (cancelled > 0) {
|
} else if (cancelled > 0) {
|
||||||
pkg.status = success > 0 ? "failed" : "cancelled";
|
pkg.status = success > 0 ? "completed" : "cancelled";
|
||||||
} else if (success > 0) {
|
} else if (success > 0) {
|
||||||
pkg.status = "completed";
|
pkg.status = "completed";
|
||||||
}
|
}
|
||||||
@ -4927,9 +4926,12 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (writeMode === "w" && item.totalBytes && item.totalBytes > 0 && process.platform === "win32") {
|
if (writeMode === "w" && item.totalBytes && item.totalBytes > 0 && process.platform === "win32") {
|
||||||
try {
|
try {
|
||||||
const fd = await fs.promises.open(effectiveTargetPath, "w");
|
const fd = await fs.promises.open(effectiveTargetPath, "w");
|
||||||
|
try {
|
||||||
await fd.truncate(item.totalBytes);
|
await fd.truncate(item.totalBytes);
|
||||||
await fd.close();
|
|
||||||
preAllocated = true;
|
preAllocated = true;
|
||||||
|
} finally {
|
||||||
|
await fd.close();
|
||||||
|
}
|
||||||
} catch { /* best-effort */ }
|
} catch { /* best-effort */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5281,6 +5283,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (!stream.destroyed) {
|
if (!stream.destroyed) {
|
||||||
stream.destroy();
|
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).
|
// 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) {
|
} else if (failed > 0) {
|
||||||
pkg.status = "failed";
|
pkg.status = "failed";
|
||||||
} else if (cancelled > 0) {
|
} else if (cancelled > 0) {
|
||||||
pkg.status = success > 0 ? "failed" : "cancelled";
|
pkg.status = success > 0 ? "completed" : "cancelled";
|
||||||
} else {
|
} else {
|
||||||
pkg.status = "completed";
|
pkg.status = "completed";
|
||||||
}
|
}
|
||||||
@ -6504,21 +6511,19 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (event) {
|
if (event) {
|
||||||
bandwidthSamples.push({
|
bandwidthSamples.push({
|
||||||
timestamp: event.at,
|
timestamp: event.at,
|
||||||
speedBps: event.bytes * 3
|
speedBps: Math.floor(event.bytes * (1000 / 120))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const paused = this.session.running && this.session.paused;
|
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;
|
let maxSpeed = 0;
|
||||||
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
||||||
const event = this.speedEvents[i];
|
const event = this.speedEvents[i];
|
||||||
if (event) {
|
if (event) {
|
||||||
totalBytes += event.bytes;
|
const speed = Math.floor(event.bytes * (1000 / 120));
|
||||||
const speed = event.bytes * 3;
|
|
||||||
if (speed > maxSpeed) {
|
if (speed > maxSpeed) {
|
||||||
maxSpeed = speed;
|
maxSpeed = speed;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -233,9 +233,9 @@ const BandwidthChart = memo(function BandwidthChart({ items, running, paused, sp
|
|||||||
|
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "top";
|
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("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) {
|
if (history.length < 2) {
|
||||||
ctx.fillStyle = textColor;
|
ctx.fillStyle = textColor;
|
||||||
@ -2320,9 +2320,9 @@ export function App(): ReactElement {
|
|||||||
</span>
|
</span>
|
||||||
{selectedHistoryIds.size > 0 && (
|
{selectedHistoryIds.size > 0 && (
|
||||||
<button className="btn btn-danger" onClick={() => {
|
<button className="btn btn-danger" onClick={() => {
|
||||||
const ids = [...selectedHistoryIds];
|
const idSet = new Set(selectedHistoryIds);
|
||||||
void Promise.all(ids.map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
void Promise.all([...idSet].map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
||||||
setHistoryEntries((prev) => prev.filter((e) => !selectedHistoryIds.has(e.id)));
|
setHistoryEntries((prev) => prev.filter((e) => !idSet.has(e.id)));
|
||||||
setSelectedHistoryIds(new Set());
|
setSelectedHistoryIds(new Set());
|
||||||
});
|
});
|
||||||
}}>Ausgewählte entfernen ({selectedHistoryIds.size})</button>
|
}}>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 failed = items.filter((item) => item.status === "failed").length;
|
||||||
const cancelled = items.filter((item) => item.status === "cancelled").length;
|
const cancelled = items.filter((item) => item.status === "cancelled").length;
|
||||||
const extracted = items.filter((item) => item.fullStatus?.startsWith("Entpackt")).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);
|
const total = Math.max(1, items.length);
|
||||||
// Use 50/50 split when extraction is active OR package is in extracting state
|
// Use 50/50 split when extraction is active OR package is in extracting state
|
||||||
// (prevents bar jumping from 100% to 50% when extraction starts)
|
// (prevents bar jumping from 100% to 50% when extraction starts)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user