Release v1.6.17
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b02aef2af9
commit
10bae4f98b
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.16",
|
"version": "1.6.17",
|
||||||
"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",
|
||||||
|
|||||||
@ -2919,6 +2919,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.session.running = true;
|
this.session.running = true;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const JVM_EXTRACTOR_REQUIRED_LIBS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// ── subst drive mapping for long paths on Windows ──
|
// ── subst drive mapping for long paths on Windows ──
|
||||||
const SUBST_THRESHOLD = 100;
|
const SUBST_THRESHOLD = 200;
|
||||||
const activeSubstDrives = new Set<string>();
|
const activeSubstDrives = new Set<string>();
|
||||||
|
|
||||||
function findFreeSubstDrive(): string | null {
|
function findFreeSubstDrive(): string | null {
|
||||||
@ -2118,7 +2118,12 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
|||||||
|
|
||||||
const workerCount = Math.min(maxParallel, parallelQueue.length);
|
const workerCount = Math.min(maxParallel, parallelQueue.length);
|
||||||
logger.info(`Parallele Extraktion: ${workerCount} gleichzeitige Worker für ${parallelQueue.length} Archive`);
|
logger.info(`Parallele Extraktion: ${workerCount} gleichzeitige Worker für ${parallelQueue.length} Archive`);
|
||||||
|
// Snapshot passwordCandidates before parallel extraction to avoid concurrent mutation.
|
||||||
|
// Each worker reads the same promoted order from the serial password-discovery pass.
|
||||||
|
const frozenPasswords = [...passwordCandidates];
|
||||||
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
||||||
|
// Restore passwordCandidates from frozen snapshot (parallel mutations are discarded).
|
||||||
|
passwordCandidates = frozenPasswords;
|
||||||
|
|
||||||
if (abortError) throw new Error("aborted:extract");
|
if (abortError) throw new Error("aborted:extract");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,7 +140,8 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
|||||||
theme: VALID_THEMES.has(settings.theme) ? settings.theme : defaults.theme,
|
theme: VALID_THEMES.has(settings.theme) ? settings.theme : defaults.theme,
|
||||||
bandwidthSchedules: normalizeBandwidthSchedules(settings.bandwidthSchedules),
|
bandwidthSchedules: normalizeBandwidthSchedules(settings.bandwidthSchedules),
|
||||||
columnOrder: normalizeColumnOrder(settings.columnOrder),
|
columnOrder: normalizeColumnOrder(settings.columnOrder),
|
||||||
extractCpuPriority: settings.extractCpuPriority
|
extractCpuPriority: settings.extractCpuPriority,
|
||||||
|
autoExtractWhenStopped: settings.autoExtractWhenStopped !== undefined ? Boolean(settings.autoExtractWhenStopped) : defaults.autoExtractWhenStopped
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
||||||
|
|||||||
@ -539,7 +539,6 @@ export function App(): ReactElement {
|
|||||||
const loadHistory = async (): Promise<void> => {
|
const loadHistory = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const entries = await window.rd.getHistory();
|
const entries = await window.rd.getHistory();
|
||||||
console.log("History loaded:", entries);
|
|
||||||
if (mountedRef.current && entries) {
|
if (mountedRef.current && entries) {
|
||||||
setHistoryEntries(entries);
|
setHistoryEntries(entries);
|
||||||
}
|
}
|
||||||
@ -1396,24 +1395,17 @@ export function App(): ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeCollectorTab = (id: string): void => {
|
const removeCollectorTab = (id: string): void => {
|
||||||
let fallbackId = "";
|
|
||||||
setCollectorTabs((prev) => {
|
setCollectorTabs((prev) => {
|
||||||
if (prev.length <= 1) {
|
if (prev.length <= 1) return prev;
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const index = prev.findIndex((tabEntry) => tabEntry.id === id);
|
const index = prev.findIndex((tabEntry) => tabEntry.id === id);
|
||||||
if (index < 0) {
|
if (index < 0) return prev;
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const next = prev.filter((tabEntry) => tabEntry.id !== id);
|
const next = prev.filter((tabEntry) => tabEntry.id !== id);
|
||||||
if (activeCollectorTabRef.current === id) {
|
if (activeCollectorTabRef.current === id) {
|
||||||
fallbackId = next[Math.max(0, index - 1)]?.id ?? next[0]?.id ?? "";
|
const fallbackId = next[Math.max(0, index - 1)]?.id ?? next[0]?.id ?? "";
|
||||||
|
if (fallbackId) setTimeout(() => setActiveCollectorTab(fallbackId), 0);
|
||||||
}
|
}
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
if (fallbackId) {
|
|
||||||
setActiveCollectorTab(fallbackId);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPackageDragStart = useCallback((packageId: string) => {
|
const onPackageDragStart = useCallback((packageId: string) => {
|
||||||
@ -1817,7 +1809,10 @@ export function App(): ReactElement {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent): void => {
|
const onKey = (e: KeyboardEvent): void => {
|
||||||
if (e.key === "Escape") setSelectedIds(new Set());
|
if (e.key === "Escape") {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") setSelectedIds(new Set());
|
||||||
|
}
|
||||||
if (e.key === "Delete" && selectedIds.size > 0) {
|
if (e.key === "Delete" && selectedIds.size > 0) {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return;
|
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return;
|
||||||
@ -2067,22 +2062,25 @@ export function App(): ReactElement {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = Math.max(1, Math.min(50, Number(e.target.value) || 1));
|
const val = Math.max(1, Math.min(50, Number(e.target.value) || 1));
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
||||||
void window.rd.updateSettings({ maxParallel: val }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ maxParallel: val }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="menu-spinner-arrows">
|
<div className="menu-spinner-arrows">
|
||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
const val = Math.min(50, settingsDraft.maxParallel + 1);
|
const val = Math.min(50, settingsDraft.maxParallel + 1);
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
||||||
void window.rd.updateSettings({ maxParallel: val }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ maxParallel: val }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
}}>▲</button>
|
}}>▲</button>
|
||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
const val = Math.max(1, settingsDraft.maxParallel - 1);
|
const val = Math.max(1, settingsDraft.maxParallel - 1);
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
setSettingsDraft((prev) => ({ ...prev, maxParallel: val }));
|
||||||
void window.rd.updateSettings({ maxParallel: val }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ maxParallel: val }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
}}>▼</button>
|
}}>▼</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2095,8 +2093,9 @@ export function App(): ReactElement {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const next = e.target.checked;
|
const next = e.target.checked;
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, speedLimitEnabled: next }));
|
setSettingsDraft((prev) => ({ ...prev, speedLimitEnabled: next }));
|
||||||
void window.rd.updateSettings({ speedLimitEnabled: next }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ speedLimitEnabled: next }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className={`menu-spinner${!settingsDraft.speedLimitEnabled ? " disabled" : ""}`}>
|
<div className={`menu-spinner${!settingsDraft.speedLimitEnabled ? " disabled" : ""}`}>
|
||||||
@ -2115,8 +2114,9 @@ export function App(): ReactElement {
|
|||||||
}
|
}
|
||||||
const kbps = Math.floor(parsed * 1024);
|
const kbps = Math.floor(parsed * 1024);
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: kbps }));
|
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: kbps }));
|
||||||
void window.rd.updateSettings({ speedLimitKbps: kbps }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ speedLimitKbps: kbps }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
setSpeedLimitInput(formatMbpsInputFromKbps(kbps));
|
setSpeedLimitInput(formatMbpsInputFromKbps(kbps));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -2125,16 +2125,18 @@ export function App(): ReactElement {
|
|||||||
const cur = (settingsDraft.speedLimitKbps || 0) / 1024;
|
const cur = (settingsDraft.speedLimitKbps || 0) / 1024;
|
||||||
const next = Math.floor((cur + 1) * 1024);
|
const next = Math.floor((cur + 1) * 1024);
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
|
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
|
||||||
void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
setSpeedLimitInput(formatMbpsInputFromKbps(next));
|
setSpeedLimitInput(formatMbpsInputFromKbps(next));
|
||||||
}}>▲</button>
|
}}>▲</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));
|
||||||
settingsDirtyRef.current = true;
|
settingsDirtyRef.current = true;
|
||||||
|
const rev = ++settingsDraftRevisionRef.current;
|
||||||
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
|
setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next }));
|
||||||
void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { settingsDirtyRef.current = false; });
|
void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; });
|
||||||
setSpeedLimitInput(formatMbpsInputFromKbps(next));
|
setSpeedLimitInput(formatMbpsInputFromKbps(next));
|
||||||
}}>▼</button>
|
}}>▼</button>
|
||||||
</div>
|
</div>
|
||||||
@ -2613,7 +2615,7 @@ export function App(): ReactElement {
|
|||||||
<label>Paketname (optional)</label>
|
<label>Paketname (optional)</label>
|
||||||
<input value={settingsDraft.packageName} onChange={(e) => setText("packageName", e.target.value)} />
|
<input value={settingsDraft.packageName} onChange={(e) => setText("packageName", e.target.value)} />
|
||||||
<div className="field-grid two">
|
<div className="field-grid two">
|
||||||
<div><label>Max. Downloads</label><input type="number" min={1} max={50} value={settingsDraft.maxParallel} onChange={(e) => setNum("maxParallel", Number(e.target.value) || 1)} /></div>
|
<div><label>Max. Downloads</label><input type="number" min={1} max={50} value={settingsDraft.maxParallel} onChange={(e) => setNum("maxParallel", Math.max(1, Math.min(50, Number(e.target.value) || 1)))} /></div>
|
||||||
<div><label>Auto-Retry Limit (0 = inf)</label><input type="number" min={0} max={99} value={settingsDraft.retryLimit} onChange={(e) => setNum("retryLimit", Math.max(0, Math.min(99, Number(e.target.value) || 0)))} /></div>
|
<div><label>Auto-Retry Limit (0 = inf)</label><input type="number" min={0} max={99} value={settingsDraft.retryLimit} onChange={(e) => setNum("retryLimit", Math.max(0, Math.min(99, Number(e.target.value) || 0)))} /></div>
|
||||||
</div>
|
</div>
|
||||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoResumeOnStart} onChange={(e) => setBool("autoResumeOnStart", e.target.checked)} /> Auto-Resume beim Start</label>
|
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoResumeOnStart} onChange={(e) => setBool("autoResumeOnStart", e.target.checked)} /> Auto-Resume beim Start</label>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user