Release v1.5.91
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
254612a49b
commit
818bf40a9c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.90",
|
"version": "1.5.91",
|
||||||
"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 = 3;
|
export const SPEED_WINDOW_SECONDS = 2;
|
||||||
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";
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {
|
|||||||
StartConflictResolutionResult,
|
StartConflictResolutionResult,
|
||||||
UiSnapshot
|
UiSnapshot
|
||||||
} from "../shared/types";
|
} from "../shared/types";
|
||||||
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, 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 { collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates } from "./extractor";
|
||||||
@ -852,11 +852,15 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setSettings(next: AppSettings): void {
|
public setSettings(next: AppSettings): void {
|
||||||
|
const prevCleanupPolicy = this.settings.completedCleanupPolicy;
|
||||||
next.totalDownloadedAllTime = Math.max(next.totalDownloadedAllTime || 0, this.settings.totalDownloadedAllTime || 0);
|
next.totalDownloadedAllTime = Math.max(next.totalDownloadedAllTime || 0, this.settings.totalDownloadedAllTime || 0);
|
||||||
this.settings = next;
|
this.settings = next;
|
||||||
this.debridService.setSettings(next);
|
this.debridService.setSettings(next);
|
||||||
this.resolveExistingQueuedOpaqueFilenames();
|
this.resolveExistingQueuedOpaqueFilenames();
|
||||||
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (setSettings): ${compactErrorText(err)}`));
|
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (setSettings): ${compactErrorText(err)}`));
|
||||||
|
if (prevCleanupPolicy !== next.completedCleanupPolicy && next.completedCleanupPolicy !== "never") {
|
||||||
|
this.applyRetroactiveCleanupPolicy();
|
||||||
|
}
|
||||||
this.emitState();
|
this.emitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -876,7 +880,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
this.pruneSpeedEvents(now);
|
this.pruneSpeedEvents(now);
|
||||||
const paused = this.session.running && this.session.paused;
|
const paused = this.session.running && this.session.paused;
|
||||||
const speedBps = paused ? 0 : this.speedBytesLastWindow / 3;
|
const speedBps = !this.session.running || paused ? 0 : this.speedBytesLastWindow / SPEED_WINDOW_SECONDS;
|
||||||
|
|
||||||
let totalItems = 0;
|
let totalItems = 0;
|
||||||
let doneItems = 0;
|
let doneItems = 0;
|
||||||
@ -926,8 +930,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
canPause: this.session.running,
|
canPause: this.session.running,
|
||||||
clipboardActive: this.settings.clipboardWatch,
|
clipboardActive: this.settings.clipboardWatch,
|
||||||
reconnectSeconds: Math.ceil(reconnectMs / 1000),
|
reconnectSeconds: Math.ceil(reconnectMs / 1000),
|
||||||
packageSpeedBps: paused ? {} : Object.fromEntries(
|
packageSpeedBps: !this.session.running || paused ? {} : Object.fromEntries(
|
||||||
[...this.speedBytesPerPackage].map(([pid, bytes]) => [pid, Math.floor(bytes / 3)])
|
[...this.speedBytesPerPackage].map(([pid, bytes]) => [pid, Math.floor(bytes / SPEED_WINDOW_SECONDS)])
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -2863,6 +2867,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
this.lastGlobalProgressBytes = this.session.totalDownloadedBytes;
|
this.lastGlobalProgressBytes = this.session.totalDownloadedBytes;
|
||||||
this.lastGlobalProgressAt = nowMs();
|
this.lastGlobalProgressAt = nowMs();
|
||||||
|
this.speedEvents = [];
|
||||||
|
this.speedBytesLastWindow = 0;
|
||||||
|
this.speedBytesPerPackage.clear();
|
||||||
|
this.speedEventsHead = 0;
|
||||||
this.abortPostProcessing("stop");
|
this.abortPostProcessing("stop");
|
||||||
for (const active of this.activeTasks.values()) {
|
for (const active of this.activeTasks.values()) {
|
||||||
active.abortReason = "stop";
|
active.abortReason = "stop";
|
||||||
@ -2970,6 +2978,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
// When pausing: abort active extractions so they don't continue during pause
|
// When pausing: abort active extractions so they don't continue during pause
|
||||||
if (!wasPaused && this.session.paused) {
|
if (!wasPaused && this.session.paused) {
|
||||||
this.abortPostProcessing("pause");
|
this.abortPostProcessing("pause");
|
||||||
|
this.speedEvents = [];
|
||||||
|
this.speedBytesLastWindow = 0;
|
||||||
|
this.speedBytesPerPackage.clear();
|
||||||
|
this.speedEventsHead = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When unpausing: clear all retry delays so stuck queued items restart immediately,
|
// When unpausing: clear all retry delays so stuck queued items restart immediately,
|
||||||
@ -3127,6 +3139,57 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyRetroactiveCleanupPolicy(): void {
|
||||||
|
const policy = this.settings.completedCleanupPolicy;
|
||||||
|
if (policy === "never") return;
|
||||||
|
|
||||||
|
let removed = 0;
|
||||||
|
for (const pkgId of [...this.session.packageOrder]) {
|
||||||
|
const pkg = this.session.packages[pkgId];
|
||||||
|
if (!pkg) continue;
|
||||||
|
|
||||||
|
if (policy === "immediate") {
|
||||||
|
const completedItemIds = pkg.itemIds.filter((itemId) => {
|
||||||
|
const item = this.session.items[itemId];
|
||||||
|
if (!item || item.status !== "completed") return false;
|
||||||
|
if (this.settings.autoExtract) return isExtractedLabel(item.fullStatus || "");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
for (const itemId of completedItemIds) {
|
||||||
|
pkg.itemIds = pkg.itemIds.filter((id) => id !== itemId);
|
||||||
|
this.releaseTargetPath(itemId);
|
||||||
|
this.dropItemContribution(itemId);
|
||||||
|
delete this.session.items[itemId];
|
||||||
|
this.itemCount = Math.max(0, this.itemCount - 1);
|
||||||
|
this.retryAfterByItem.delete(itemId);
|
||||||
|
removed += 1;
|
||||||
|
}
|
||||||
|
if (pkg.itemIds.length === 0) {
|
||||||
|
this.removePackageFromSession(pkgId, []);
|
||||||
|
}
|
||||||
|
} else if (policy === "package_done" || policy === "on_start") {
|
||||||
|
const allCompleted = pkg.itemIds.every((id) => {
|
||||||
|
const item = this.session.items[id];
|
||||||
|
return !item || item.status === "completed";
|
||||||
|
});
|
||||||
|
if (!allCompleted) continue;
|
||||||
|
if (this.settings.autoExtract) {
|
||||||
|
const allExtracted = pkg.itemIds.every((id) => {
|
||||||
|
const item = this.session.items[id];
|
||||||
|
return !item || isExtractedLabel(item.fullStatus || "");
|
||||||
|
});
|
||||||
|
if (!allExtracted) continue;
|
||||||
|
}
|
||||||
|
removed += pkg.itemIds.length;
|
||||||
|
this.removePackageFromSession(pkgId, [...pkg.itemIds], "completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed > 0) {
|
||||||
|
logger.info(`Retroaktive Bereinigung: ${removed} fertige Items entfernt (policy=${policy})`);
|
||||||
|
this.persistSoon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private clearPersistTimer(): void {
|
private clearPersistTimer(): void {
|
||||||
if (!this.persistTimer) {
|
if (!this.persistTimer) {
|
||||||
return;
|
return;
|
||||||
@ -3199,12 +3262,12 @@ 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
|
||||||
? 1200
|
? 900
|
||||||
: itemCount >= 700
|
: itemCount >= 700
|
||||||
? 900
|
? 650
|
||||||
: itemCount >= 250
|
: itemCount >= 250
|
||||||
? 560
|
? 400
|
||||||
: 320
|
: 250
|
||||||
: 260;
|
: 260;
|
||||||
this.stateEmitTimer = setTimeout(() => {
|
this.stateEmitTimer = setTimeout(() => {
|
||||||
this.stateEmitTimer = null;
|
this.stateEmitTimer = null;
|
||||||
@ -3217,7 +3280,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
private speedBytesPerPackage = new Map<string, number>();
|
private speedBytesPerPackage = new Map<string, number>();
|
||||||
|
|
||||||
private pruneSpeedEvents(now: number): void {
|
private pruneSpeedEvents(now: number): void {
|
||||||
const cutoff = now - 3000;
|
const cutoff = now - SPEED_WINDOW_SECONDS * 1000;
|
||||||
while (this.speedEventsHead < this.speedEvents.length && this.speedEvents[this.speedEventsHead].at < cutoff) {
|
while (this.speedEventsHead < this.speedEvents.length && this.speedEvents[this.speedEventsHead].at < cutoff) {
|
||||||
const ev = this.speedEvents[this.speedEventsHead];
|
const ev = this.speedEvents[this.speedEventsHead];
|
||||||
this.speedBytesLastWindow = Math.max(0, this.speedBytesLastWindow - ev.bytes);
|
this.speedBytesLastWindow = Math.max(0, this.speedBytesLastWindow - ev.bytes);
|
||||||
@ -3458,9 +3521,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.autoExtract && failed === 0 && success > 0) {
|
if (this.settings.autoExtract && failed === 0 && success > 0) {
|
||||||
const needsPostProcess = pkg.status !== "completed"
|
const needsExtraction = items.some((item) => item.status === "completed" && !isExtractedLabel(item.fullStatus));
|
||||||
|| items.some((item) => item.status === "completed" && !isExtractedLabel(item.fullStatus));
|
if (needsExtraction) {
|
||||||
if (needsPostProcess) {
|
|
||||||
pkg.status = "queued";
|
pkg.status = "queued";
|
||||||
pkg.updatedAt = nowMs();
|
pkg.updatedAt = nowMs();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@ -5080,9 +5142,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
throughputWindowBytes = 0;
|
throughputWindowBytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsed = Math.max((nowMs() - windowStarted) / 1000, 0.5);
|
const elapsed = Math.max((nowMs() - windowStarted) / 1000, 0.3);
|
||||||
const speed = windowBytes / elapsed;
|
const speed = windowBytes / elapsed;
|
||||||
if (elapsed >= 1.2) {
|
if (elapsed >= 0.8) {
|
||||||
windowStarted = nowMs();
|
windowStarted = nowMs();
|
||||||
windowBytes = 0;
|
windowBytes = 0;
|
||||||
}
|
}
|
||||||
@ -5659,6 +5721,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
logger.info(`Hybrid-Extract Start: pkg=${pkg.name}, readyArchives=${readyArchives.size}`);
|
logger.info(`Hybrid-Extract Start: pkg=${pkg.name}, readyArchives=${readyArchives.size}`);
|
||||||
pkg.status = "extracting";
|
pkg.status = "extracting";
|
||||||
this.emitState();
|
this.emitState();
|
||||||
|
const hybridExtractStartMs = nowMs();
|
||||||
|
|
||||||
const completedItems = items.filter((item) => item.status === "completed");
|
const completedItems = items.filter((item) => item.status === "completed");
|
||||||
|
|
||||||
@ -5825,7 +5888,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const status = entry.fullStatus || "";
|
const status = entry.fullStatus || "";
|
||||||
if (/^Entpacken\b/i.test(status) || /^Fertig\b/i.test(status)) {
|
if (/^Entpacken\b/i.test(status) || /^Fertig\b/i.test(status)) {
|
||||||
if (result.extracted > 0 && result.failed === 0) {
|
if (result.extracted > 0 && result.failed === 0) {
|
||||||
entry.fullStatus = "Entpackt - Done";
|
entry.fullStatus = formatExtractDone(nowMs() - hybridExtractStartMs);
|
||||||
} else {
|
} else {
|
||||||
entry.fullStatus = "Entpacken - Error";
|
entry.fullStatus = "Entpacken - Error";
|
||||||
}
|
}
|
||||||
@ -5920,6 +5983,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) {
|
if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) {
|
||||||
pkg.status = "extracting";
|
pkg.status = "extracting";
|
||||||
this.emitState();
|
this.emitState();
|
||||||
|
const extractionStartMs = nowMs();
|
||||||
|
|
||||||
const resolveArchiveItems = (archiveName: string): DownloadItem[] =>
|
const resolveArchiveItems = (archiveName: string): DownloadItem[] =>
|
||||||
resolveArchiveItemsFromList(archiveName, completedItems);
|
resolveArchiveItemsFromList(archiveName, completedItems);
|
||||||
@ -6094,7 +6158,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
let finalStatusText = "";
|
let finalStatusText = "";
|
||||||
|
|
||||||
if (result.extracted > 0 || hasExtractedOutput) {
|
if (result.extracted > 0 || hasExtractedOutput) {
|
||||||
finalStatusText = "Entpackt - Done";
|
finalStatusText = formatExtractDone(nowMs() - extractionStartMs);
|
||||||
} else if (!sourceExists) {
|
} else if (!sourceExists) {
|
||||||
finalStatusText = "Entpackt (Quelle fehlt)";
|
finalStatusText = "Entpackt (Quelle fehlt)";
|
||||||
logger.warn(`Post-Processing ohne Quellordner: pkg=${pkg.name}, outputDir fehlt`);
|
logger.warn(`Post-Processing ohne Quellordner: pkg=${pkg.name}, outputDir fehlt`);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user