Release v1.5.95
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4fcbd5c4f7
commit
00fae5cadd
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.94",
|
"version": "1.5.95",
|
||||||
"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",
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const RAR_SPLIT_RE = /\.r\d{2,3}$/i;
|
|||||||
|
|
||||||
export const MAX_MANIFEST_FILE_BYTES = 5 * 1024 * 1024;
|
export const MAX_MANIFEST_FILE_BYTES = 5 * 1024 * 1024;
|
||||||
export const MAX_LINK_ARTIFACT_BYTES = 256 * 1024;
|
export const MAX_LINK_ARTIFACT_BYTES = 256 * 1024;
|
||||||
export const SPEED_WINDOW_SECONDS = 2;
|
export const SPEED_WINDOW_SECONDS = 1;
|
||||||
export const CLIPBOARD_POLL_INTERVAL_MS = 2000;
|
export const CLIPBOARD_POLL_INTERVAL_MS = 2000;
|
||||||
|
|
||||||
export const DEFAULT_UPDATE_REPO = "Sucukdeluxe/real-debrid-downloader";
|
export const DEFAULT_UPDATE_REPO = "Sucukdeluxe/real-debrid-downloader";
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import {
|
|||||||
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, SPEED_WINDOW_SECONDS, WRITE_BUFFER_SIZE, WRITE_FLUSH_TIMEOUT_MS, ALLOCATION_UNIT_SIZE, STREAM_HIGH_WATER_MARK } from "./constants";
|
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, SPEED_WINDOW_SECONDS, WRITE_BUFFER_SIZE, WRITE_FLUSH_TIMEOUT_MS, ALLOCATION_UNIT_SIZE, STREAM_HIGH_WATER_MARK } from "./constants";
|
||||||
import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
|
import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
|
||||||
import { DebridService, MegaWebUnrestrictor, checkRapidgatorOnline } from "./debrid";
|
import { DebridService, MegaWebUnrestrictor, checkRapidgatorOnline } from "./debrid";
|
||||||
import { collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates } from "./extractor";
|
import { clearExtractResumeState, collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates } from "./extractor";
|
||||||
import { validateFileAgainstManifest } from "./integrity";
|
import { validateFileAgainstManifest } from "./integrity";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { StoragePaths, saveSession, saveSessionAsync, saveSettings, saveSettingsAsync } from "./storage";
|
import { StoragePaths, saveSession, saveSessionAsync, saveSettings, saveSettingsAsync } from "./storage";
|
||||||
@ -2476,7 +2476,13 @@ export class DownloadManager extends EventEmitter {
|
|||||||
postProcessController.abort("reset");
|
postProcessController.abort("reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Reset package state
|
// 3. Clean up extraction progress manifest (.rd_extract_progress.json)
|
||||||
|
if (pkg.outputDir) {
|
||||||
|
clearExtractResumeState(pkg.outputDir, packageId).catch(() => {});
|
||||||
|
clearExtractResumeState(pkg.outputDir).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Reset package state
|
||||||
pkg.status = "queued";
|
pkg.status = "queued";
|
||||||
pkg.cancelled = false;
|
pkg.cancelled = false;
|
||||||
pkg.enabled = true;
|
pkg.enabled = true;
|
||||||
@ -3305,13 +3311,13 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const itemCount = this.itemCount;
|
const itemCount = this.itemCount;
|
||||||
const emitDelay = this.session.running
|
const emitDelay = this.session.running
|
||||||
? itemCount >= 1500
|
? itemCount >= 1500
|
||||||
? 900
|
? 700
|
||||||
: itemCount >= 700
|
: itemCount >= 700
|
||||||
? 650
|
? 500
|
||||||
: itemCount >= 250
|
: itemCount >= 250
|
||||||
? 400
|
? 300
|
||||||
: 250
|
: 150
|
||||||
: 260;
|
: 200;
|
||||||
this.stateEmitTimer = setTimeout(() => {
|
this.stateEmitTimer = setTimeout(() => {
|
||||||
this.stateEmitTimer = null;
|
this.stateEmitTimer = null;
|
||||||
this.lastStateEmitAt = nowMs();
|
this.lastStateEmitAt = nowMs();
|
||||||
@ -3729,8 +3735,15 @@ export class DownloadManager extends EventEmitter {
|
|||||||
delete this.session.packages[packageId];
|
delete this.session.packages[packageId];
|
||||||
this.session.packageOrder = this.session.packageOrder.filter((id) => id !== packageId);
|
this.session.packageOrder = this.session.packageOrder.filter((id) => id !== packageId);
|
||||||
// Keep packageId in runPackageIds so the "size > 0" guard still filters
|
// Keep packageId in runPackageIds so the "size > 0" guard still filters
|
||||||
// other packages. The deleted package has no items left, so the scheduler
|
// other packages. But prune ghost entries: if no real package remains in
|
||||||
// simply won't find anything for it. finishRun() clears runPackageIds.
|
// the set, clear it so the scheduler isn't permanently blocked.
|
||||||
|
if (this.runPackageIds.size > 0) {
|
||||||
|
for (const rpId of this.runPackageIds) {
|
||||||
|
if (!this.session.packages[rpId]) {
|
||||||
|
this.runPackageIds.delete(rpId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
this.runCompletedPackages.delete(packageId);
|
this.runCompletedPackages.delete(packageId);
|
||||||
this.hybridExtractRequeue.delete(packageId);
|
this.hybridExtractRequeue.delete(packageId);
|
||||||
this.resetSessionTotalsIfQueueEmpty();
|
this.resetSessionTotalsIfQueueEmpty();
|
||||||
@ -4920,12 +4933,12 @@ export class DownloadManager extends EventEmitter {
|
|||||||
let windowStarted = nowMs();
|
let windowStarted = nowMs();
|
||||||
const itemCount = this.itemCount;
|
const itemCount = this.itemCount;
|
||||||
const uiUpdateIntervalMs = itemCount >= 1500
|
const uiUpdateIntervalMs = itemCount >= 1500
|
||||||
? 650
|
? 500
|
||||||
: itemCount >= 700
|
: itemCount >= 700
|
||||||
? 420
|
? 350
|
||||||
: itemCount >= 250
|
: itemCount >= 250
|
||||||
? 280
|
? 220
|
||||||
: 170;
|
: 120;
|
||||||
let lastUiEmitAt = 0;
|
let lastUiEmitAt = 0;
|
||||||
const stallTimeoutMs = getDownloadStallTimeoutMs();
|
const stallTimeoutMs = getDownloadStallTimeoutMs();
|
||||||
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
|
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
|
||||||
@ -5187,9 +5200,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
throughputWindowBytes = 0;
|
throughputWindowBytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsed = Math.max((nowMs() - windowStarted) / 1000, 0.3);
|
const elapsed = Math.max((nowMs() - windowStarted) / 1000, 0.2);
|
||||||
const speed = windowBytes / elapsed;
|
const speed = windowBytes / elapsed;
|
||||||
if (elapsed >= 0.8) {
|
if (elapsed >= 0.5) {
|
||||||
windowStarted = nowMs();
|
windowStarted = nowMs();
|
||||||
windowBytes = 0;
|
windowBytes = 0;
|
||||||
}
|
}
|
||||||
@ -5882,13 +5895,12 @@ export class DownloadManager extends EventEmitter {
|
|||||||
activeHybridArchiveMap.delete(progress.archiveName);
|
activeHybridArchiveMap.delete(progress.archiveName);
|
||||||
hybridArchiveStartTimes.delete(progress.archiveName);
|
hybridArchiveStartTimes.delete(progress.archiveName);
|
||||||
} else {
|
} else {
|
||||||
// Update this archive's items with current progress
|
// Update this archive's items with per-archive progress
|
||||||
const archive = ` · ${progress.archiveName}`;
|
const archiveLabel = ` · ${progress.archiveName}`;
|
||||||
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
||||||
? ` · ${Math.floor(progress.elapsedMs / 1000)}s`
|
? ` · ${Math.floor(progress.elapsedMs / 1000)}s`
|
||||||
: "";
|
: "";
|
||||||
const activeArchive = Number(progress.archivePercent ?? 0) > 0 ? 1 : 0;
|
const archivePct = Math.max(0, Math.min(100, Math.floor(Number(progress.archivePercent ?? 0))));
|
||||||
const currentDisplay = Math.max(0, Math.min(progress.total, progress.current + activeArchive));
|
|
||||||
let label: string;
|
let label: string;
|
||||||
if (progress.passwordFound) {
|
if (progress.passwordFound) {
|
||||||
label = `Passwort gefunden · ${progress.archiveName}`;
|
label = `Passwort gefunden · ${progress.archiveName}`;
|
||||||
@ -5896,7 +5908,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const pwPct = Math.round((progress.passwordAttempt / progress.passwordTotal) * 100);
|
const pwPct = Math.round((progress.passwordAttempt / progress.passwordTotal) * 100);
|
||||||
label = `Passwort knacken: ${pwPct}% (${progress.passwordAttempt}/${progress.passwordTotal}) · ${progress.archiveName}`;
|
label = `Passwort knacken: ${pwPct}% (${progress.passwordAttempt}/${progress.passwordTotal}) · ${progress.archiveName}`;
|
||||||
} else {
|
} else {
|
||||||
label = `Entpacken ${progress.percent}% (${currentDisplay}/${progress.total})${archive}${elapsed}`;
|
label = `Entpacken ${archivePct}%${archiveLabel}${elapsed}`;
|
||||||
}
|
}
|
||||||
const updatedAt = nowMs();
|
const updatedAt = nowMs();
|
||||||
for (const entry of archItems) {
|
for (const entry of archItems) {
|
||||||
@ -5908,10 +5920,17 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttled emit
|
// Throttled emit — also promote "Warten auf Parts" items that
|
||||||
|
// completed downloading in the meantime to "Ausstehend".
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
if (now - hybridLastEmitAt >= EXTRACT_PROGRESS_EMIT_INTERVAL_MS) {
|
if (now - hybridLastEmitAt >= EXTRACT_PROGRESS_EMIT_INTERVAL_MS) {
|
||||||
hybridLastEmitAt = now;
|
hybridLastEmitAt = now;
|
||||||
|
for (const entry of items) {
|
||||||
|
if (entry.status === "completed" && entry.fullStatus === "Entpacken - Warten auf Parts") {
|
||||||
|
entry.fullStatus = "Entpacken - Ausstehend";
|
||||||
|
entry.updatedAt = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.emitState();
|
this.emitState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6144,13 +6163,12 @@ export class DownloadManager extends EventEmitter {
|
|||||||
activeArchiveItemsMap.delete(progress.archiveName);
|
activeArchiveItemsMap.delete(progress.archiveName);
|
||||||
archiveStartTimes.delete(progress.archiveName);
|
archiveStartTimes.delete(progress.archiveName);
|
||||||
} else {
|
} else {
|
||||||
// Update this archive's items with current progress
|
// Update this archive's items with per-archive progress
|
||||||
const archive = progress.archiveName ? ` · ${progress.archiveName}` : "";
|
const archiveTag = progress.archiveName ? ` · ${progress.archiveName}` : "";
|
||||||
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
||||||
? ` · ${Math.floor(progress.elapsedMs / 1000)}s`
|
? ` · ${Math.floor(progress.elapsedMs / 1000)}s`
|
||||||
: "";
|
: "";
|
||||||
const activeArchive = Number(progress.archivePercent ?? 0) > 0 ? 1 : 0;
|
const archivePct = Math.max(0, Math.min(100, Math.floor(Number(progress.archivePercent ?? 0))));
|
||||||
const currentDisplay = Math.max(0, Math.min(progress.total, progress.current + activeArchive));
|
|
||||||
let label: string;
|
let label: string;
|
||||||
if (progress.passwordFound) {
|
if (progress.passwordFound) {
|
||||||
label = `Passwort gefunden · ${progress.archiveName}`;
|
label = `Passwort gefunden · ${progress.archiveName}`;
|
||||||
@ -6158,7 +6176,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const pwPct = Math.round((progress.passwordAttempt / progress.passwordTotal) * 100);
|
const pwPct = Math.round((progress.passwordAttempt / progress.passwordTotal) * 100);
|
||||||
label = `Passwort knacken: ${pwPct}% (${progress.passwordAttempt}/${progress.passwordTotal}) · ${progress.archiveName}`;
|
label = `Passwort knacken: ${pwPct}% (${progress.passwordAttempt}/${progress.passwordTotal}) · ${progress.archiveName}`;
|
||||||
} else {
|
} else {
|
||||||
label = `Entpacken ${progress.percent}% (${currentDisplay}/${progress.total})${archive}${elapsed}`;
|
label = `Entpacken ${archivePct}%${archiveTag}${elapsed}`;
|
||||||
}
|
}
|
||||||
const updatedAt = nowMs();
|
const updatedAt = nowMs();
|
||||||
for (const entry of archiveItems) {
|
for (const entry of archiveItems) {
|
||||||
|
|||||||
@ -432,7 +432,7 @@ async function writeExtractResumeState(packageDir: string, completedArchives: Se
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearExtractResumeState(packageDir: string, packageId?: string): Promise<void> {
|
export async function clearExtractResumeState(packageDir: string, packageId?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await fs.promises.rm(extractProgressFilePath(packageDir, packageId), { force: true });
|
await fs.promises.rm(extractProgressFilePath(packageDir, packageId), { force: true });
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -3082,7 +3082,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
|
|||||||
// (prevents bar jumping from 100% to 50% when extraction starts)
|
// (prevents bar jumping from 100% to 50% when extraction starts)
|
||||||
const allDownloaded = done + failed + cancelled >= total;
|
const allDownloaded = done + failed + cancelled >= total;
|
||||||
const allExtracted = extracted >= total;
|
const allExtracted = extracted >= total;
|
||||||
const useExtractSplit = extracting || pkg.status === "extracting" || (allDownloaded && !allExtracted && done > 0 && extracted > 0);
|
const useExtractSplit = extracting || pkg.status === "extracting" || (allDownloaded && !allExtracted && done > 0 && extracted > 0 && failed === 0 && cancelled === 0);
|
||||||
// Include fractional progress from active downloads so the bar moves continuously
|
// Include fractional progress from active downloads so the bar moves continuously
|
||||||
const activeProgress = items.reduce((sum, item) => {
|
const activeProgress = items.reduce((sum, item) => {
|
||||||
if (item.status === "downloading" || (item.status === "queued" && (item.progressPercent || 0) > 0)) {
|
if (item.status === "downloading" || (item.status === "queued" && (item.progressPercent || 0) > 0)) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user