Release v1.6.2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-04 05:41:40 +01:00
parent 17844d4c28
commit 1d4a13466f
4 changed files with 31 additions and 13 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.6.1", "version": "1.6.2",
"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",

View File

@ -242,7 +242,10 @@ function isUnrestrictFailure(errorText: string): boolean {
const text = String(errorText || "").toLowerCase(); const text = String(errorText || "").toLowerCase();
return text.includes("unrestrict") || text.includes("mega-web") || text.includes("mega-debrid") return text.includes("unrestrict") || text.includes("mega-web") || text.includes("mega-debrid")
|| text.includes("bestdebrid") || text.includes("alldebrid") || text.includes("kein debrid") || text.includes("bestdebrid") || text.includes("alldebrid") || text.includes("kein debrid")
|| text.includes("session") || text.includes("login"); || text.includes("session-cookie") || text.includes("session cookie") || text.includes("session blockiert")
|| text.includes("session expired") || text.includes("invalid session")
|| text.includes("login ungültig") || text.includes("login liefert")
|| text.includes("login required") || text.includes("login failed");
} }
function isProviderBusyUnrestrictError(errorText: string): boolean { function isProviderBusyUnrestrictError(errorText: string): boolean {
@ -4864,6 +4867,8 @@ export class DownloadManager extends EventEmitter {
} }
const acceptRanges = (response.headers.get("accept-ranges") || "").toLowerCase().includes("bytes"); const acceptRanges = (response.headers.get("accept-ranges") || "").toLowerCase().includes("bytes");
let preAllocated = false;
let written = 0;
try { try {
if (existingBytes === 0) { if (existingBytes === 0) {
const rawHeaderName = parseContentDispositionFilename(response.headers.get("content-disposition")).trim(); const rawHeaderName = parseContentDispositionFilename(response.headers.get("content-disposition")).trim();
@ -4919,7 +4924,6 @@ export class DownloadManager extends EventEmitter {
await fs.promises.mkdir(path.dirname(effectiveTargetPath), { recursive: true }); await fs.promises.mkdir(path.dirname(effectiveTargetPath), { recursive: true });
// Sparse file pre-allocation (Windows only, new files with known size) // Sparse file pre-allocation (Windows only, new files with known size)
let preAllocated = false;
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");
@ -4934,7 +4938,7 @@ export class DownloadManager extends EventEmitter {
start: preAllocated ? 0 : undefined, start: preAllocated ? 0 : undefined,
highWaterMark: STREAM_HIGH_WATER_MARK highWaterMark: STREAM_HIGH_WATER_MARK
}); });
let written = writeMode === "a" ? existingBytes : 0; written = writeMode === "a" ? existingBytes : 0;
let windowBytes = 0; let windowBytes = 0;
let windowStarted = nowMs(); let windowStarted = nowMs();
const itemCount = this.itemCount; const itemCount = this.itemCount;
@ -5313,6 +5317,13 @@ export class DownloadManager extends EventEmitter {
this.emitState(); this.emitState();
return { resumable }; return { resumable };
} catch (error) { } catch (error) {
// Truncate pre-allocated sparse file to actual written bytes so that
// stat.size on the next retry reflects real data, not the pre-allocated size.
// Without this, the retry reads stat.size = totalBytes and either sends an
// impossible Range header (→ 416 → false complete) or appends to a zero-padded file.
if (preAllocated && item.totalBytes && written < item.totalBytes) {
try { await fs.promises.truncate(effectiveTargetPath, written); } catch { /* best-effort */ }
}
if (active.abortController.signal.aborted || String(error).includes("aborted:")) { if (active.abortController.signal.aborted || String(error).includes("aborted:")) {
throw error; throw error;
} }

View File

@ -522,7 +522,6 @@ function winRarCandidates(): string[] {
const installed = [ const installed = [
path.join(programFiles, "WinRAR", "UnRAR.exe"), path.join(programFiles, "WinRAR", "UnRAR.exe"),
path.join(programFilesX86, "WinRAR", "UnRAR.exe"),
path.join(programFilesX86, "WinRAR", "UnRAR.exe") path.join(programFilesX86, "WinRAR", "UnRAR.exe")
]; ];

View File

@ -70,7 +70,7 @@ const emptySnapshot = (): UiSnapshot => ({
cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false, cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false,
removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true, removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true,
autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never", autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never",
maxParallel: 4, maxParallelExtract: 2, retryLimit: 0, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global", maxParallel: 4, maxParallelExtract: 2, extractCpuPriority: "high", retryLimit: 0, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global",
updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false, updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false,
theme: "dark", collapseNewPackages: true, autoSkipExtracted: false, confirmDeleteSelection: true, theme: "dark", collapseNewPackages: true, autoSkipExtracted: false, confirmDeleteSelection: true,
bandwidthSchedules: [], totalDownloadedAllTime: 0, bandwidthSchedules: [], totalDownloadedAllTime: 0,
@ -2043,14 +2043,20 @@ export function App(): ReactElement {
<input <input
type="text" type="text"
inputMode="decimal" inputMode="decimal"
value={formatMbpsInputFromKbps(settingsDraft.speedLimitKbps)} value={speedLimitInput}
onChange={(e) => { onChange={(e) => {
const parsed = parseMbpsInput(e.target.value); setSpeedLimitInput(e.target.value);
if (parsed !== null) { }}
const kbps = Math.floor(parsed * 1024); onBlur={() => {
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: kbps })); const parsed = parseMbpsInput(speedLimitInput);
void window.rd.updateSettings({ speedLimitKbps: kbps }); if (parsed === null) {
setSpeedLimitInput(formatMbpsInputFromKbps(settingsDraft.speedLimitKbps));
return;
} }
const kbps = Math.floor(parsed * 1024);
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: kbps }));
void window.rd.updateSettings({ speedLimitKbps: kbps });
setSpeedLimitInput(formatMbpsInputFromKbps(kbps));
}} }}
/> />
<div className="menu-spinner-arrows"> <div className="menu-spinner-arrows">
@ -2059,12 +2065,14 @@ export function App(): ReactElement {
const next = Math.floor((cur + 1) * 1024); const next = Math.floor((cur + 1) * 1024);
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next })); setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
void window.rd.updateSettings({ speedLimitKbps: next }); void window.rd.updateSettings({ speedLimitKbps: next });
setSpeedLimitInput(formatMbpsInputFromKbps(next));
}}>&#9650;</button> }}>&#9650;</button>
<button onClick={() => { <button onClick={() => {
const cur = (settingsDraft.speedLimitKbps || 0) / 1024; const cur = (settingsDraft.speedLimitKbps || 0) / 1024;
const next = Math.max(0, Math.floor((cur - 1) * 1024)); const next = Math.max(0, Math.floor((cur - 1) * 1024));
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next })); setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
void window.rd.updateSettings({ speedLimitKbps: next }); void window.rd.updateSettings({ speedLimitKbps: next });
setSpeedLimitInput(formatMbpsInputFromKbps(next));
}}>&#9660;</button> }}>&#9660;</button>
</div> </div>
</div> </div>
@ -2625,7 +2633,7 @@ export function App(): ReactElement {
</div> </div>
</div> </div>
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoReconnect} onChange={(e) => setBool("autoReconnect", e.target.checked)} /> Automatischer Reconnect</label> <label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoReconnect} onChange={(e) => setBool("autoReconnect", e.target.checked)} /> Automatischer Reconnect</label>
<div><label>Reconnect-Wartezeit (Sek.)</label><input type="number" min={10} max={600} value={settingsDraft.reconnectWaitSeconds} onChange={(e) => setNum("reconnectWaitSeconds", Number(e.target.value) || 45)} /></div> <div><label>Reconnect-Wartezeit (Sek.)</label><input type="number" min={10} max={600} value={settingsDraft.reconnectWaitSeconds} onChange={(e) => setNum("reconnectWaitSeconds", Math.max(10, Math.min(600, Number(e.target.value) || 45)))} /></div>
<h4>Bandbreitenplanung</h4> <h4>Bandbreitenplanung</h4>
{schedules.map((s, i) => { {schedules.map((s, i) => {
const scheduleKey = s.id || `schedule-${i}`; const scheduleKey = s.id || `schedule-${i}`;