Compare commits

..

No commits in common. "a27d0ec8f2c9883615c7b50a7a7200d06529abbe" and "c51b52b86a30197b920d21f8fbebfbe90bd1c7c8" have entirely different histories.

3 changed files with 88 additions and 120 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.7.115", "version": "1.7.114",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -3,24 +3,16 @@ import path from "node:path";
import { addLogListener, removeLogListener } from "./logger"; import { addLogListener, removeLogListener } from "./logger";
const DAILY_LOG_RETENTION_DAYS = 30; const DAILY_LOG_RETENTION_DAYS = 30;
const CLEANUP_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; const CLEANUP_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // every 6 hours
const FLUSH_INTERVAL_MS = 500;
const BUFFER_LIMIT_CHARS = 500_000;
let dailyLogDir = ""; let dailyLogDir = "";
let currentDayKey = ""; let currentDayKey = "";
let currentLogFd: number | null = null;
let currentRenameFd: number | null = null;
let logListener: ((line: string) => void) | null = null; let logListener: ((line: string) => void) | null = null;
let cleanupTimer: NodeJS.Timeout | null = null; let cleanupTimer: NodeJS.Timeout | null = null;
let lastCleanupAt = 0; let lastCleanupAt = 0;
// Async buffered writes — never blocks the event loop
let pendingLogLines: string[] = [];
let pendingLogChars = 0;
let pendingRenameLines: string[] = [];
let pendingRenameChars = 0;
let flushTimer: NodeJS.Timeout | null = null;
let flushInFlight = false;
function getDayKey(now = new Date()): string { function getDayKey(now = new Date()): string {
const year = now.getFullYear(); const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0"); const month = String(now.getMonth() + 1).padStart(2, "0");
@ -29,84 +21,59 @@ function getDayKey(now = new Date()): string {
} }
function getMonthDir(dayKey: string): string { function getMonthDir(dayKey: string): string {
return dayKey.slice(0, 7); return dayKey.slice(0, 7); // "YYYY-MM"
} }
function getDailyLogPath(dayKey: string): string { function ensureDayFile(dayKey: string): number | null {
return path.join(dailyLogDir, getMonthDir(dayKey), `${dayKey}.log`); if (currentDayKey === dayKey && currentLogFd !== null) {
} return currentLogFd;
}
function getDailyRenameLogPath(dayKey: string): string { // Close previous day's fd
return path.join(dailyLogDir, getMonthDir(dayKey), `${dayKey}-rename.log`); if (currentLogFd !== null) {
} try { fs.closeSync(currentLogFd); } catch { /* ignore */ }
currentLogFd = null;
}
if (currentRenameFd !== null) {
try { fs.closeSync(currentRenameFd); } catch { /* ignore */ }
currentRenameFd = null;
}
function scheduleFlush(): void { currentDayKey = dayKey;
if (flushTimer || flushInFlight) return; const monthDir = path.join(dailyLogDir, getMonthDir(dayKey));
flushTimer = setTimeout(() => {
flushTimer = null;
void flushAsync();
}, FLUSH_INTERVAL_MS);
}
async function flushAsync(): Promise<void> {
if (flushInFlight) return;
flushInFlight = true;
try { try {
const dayKey = currentDayKey || getDayKey(); fs.mkdirSync(monthDir, { recursive: true });
const filePath = path.join(monthDir, `${dayKey}.log`);
if (pendingLogLines.length > 0) { currentLogFd = fs.openSync(filePath, "a");
const chunk = pendingLogLines.join(""); return currentLogFd;
pendingLogLines = []; } catch {
pendingLogChars = 0; return null;
const filePath = getDailyLogPath(dayKey);
try {
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.appendFile(filePath, chunk, "utf8");
} catch { /* ignore */ }
}
if (pendingRenameLines.length > 0) {
const chunk = pendingRenameLines.join("");
pendingRenameLines = [];
pendingRenameChars = 0;
const filePath = getDailyRenameLogPath(dayKey);
try {
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.appendFile(filePath, chunk, "utf8");
} catch { /* ignore */ }
}
} finally {
flushInFlight = false;
if (pendingLogLines.length > 0 || pendingRenameLines.length > 0) {
scheduleFlush();
}
} }
} }
function flushSyncOnExit(): void { function ensureRenameFd(dayKey: string): number | null {
const dayKey = currentDayKey || getDayKey(); if (currentDayKey === dayKey && currentRenameFd !== null) {
return currentRenameFd;
if (pendingLogLines.length > 0) {
const chunk = pendingLogLines.join("");
pendingLogLines = [];
pendingLogChars = 0;
try {
const filePath = getDailyLogPath(dayKey);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.appendFileSync(filePath, chunk, "utf8");
} catch { /* ignore */ }
} }
if (pendingRenameLines.length > 0) { // ensureDayFile handles day transitions
const chunk = pendingRenameLines.join(""); if (currentDayKey !== dayKey) {
pendingRenameLines = []; ensureDayFile(dayKey);
pendingRenameChars = 0; }
try {
const filePath = getDailyRenameLogPath(dayKey); if (currentRenameFd !== null) {
fs.mkdirSync(path.dirname(filePath), { recursive: true }); return currentRenameFd;
fs.appendFileSync(filePath, chunk, "utf8"); }
} catch { /* ignore */ }
const monthDir = path.join(dailyLogDir, getMonthDir(dayKey));
try {
fs.mkdirSync(monthDir, { recursive: true });
const filePath = path.join(monthDir, `${dayKey}-rename.log`);
currentRenameFd = fs.openSync(filePath, "a");
return currentRenameFd;
} catch {
return null;
} }
} }
@ -114,43 +81,31 @@ function writeToDailyLog(line: string): void {
if (!dailyLogDir) return; if (!dailyLogDir) return;
const dayKey = getDayKey(); const dayKey = getDayKey();
if (dayKey !== currentDayKey) { const fd = ensureDayFile(dayKey);
// Day changed — flush previous day's buffer first if (fd === null) return;
if (currentDayKey && (pendingLogLines.length > 0 || pendingRenameLines.length > 0)) {
void flushAsync(); try {
} fs.writeSync(fd, line);
currentDayKey = dayKey; } catch {
// Close and retry on next write
try { fs.closeSync(fd); } catch { /* ignore */ }
currentLogFd = null;
} }
pendingLogLines.push(line);
pendingLogChars += line.length;
// Shed oldest lines if buffer too large
while (pendingLogChars > BUFFER_LIMIT_CHARS && pendingLogLines.length > 1) {
const removed = pendingLogLines.shift();
if (removed) pendingLogChars -= removed.length;
}
scheduleFlush();
} }
export function writeToDailyRenameLog(line: string): void { export function writeToDailyRenameLog(line: string): void {
if (!dailyLogDir) return; if (!dailyLogDir) return;
const dayKey = getDayKey(); const dayKey = getDayKey();
if (dayKey !== currentDayKey) { const fd = ensureRenameFd(dayKey);
currentDayKey = dayKey; if (fd === null) return;
try {
fs.writeSync(fd, line);
} catch {
try { fs.closeSync(fd); } catch { /* ignore */ }
currentRenameFd = null;
} }
pendingRenameLines.push(line);
pendingRenameChars += line.length;
while (pendingRenameChars > BUFFER_LIMIT_CHARS && pendingRenameLines.length > 1) {
const removed = pendingRenameLines.shift();
if (removed) pendingRenameChars -= removed.length;
}
scheduleFlush();
} }
function cleanupOldDailyLogs(): void { function cleanupOldDailyLogs(): void {
@ -181,6 +136,7 @@ function cleanupOldDailyLogs(): void {
} catch { /* ignore */ } } catch { /* ignore */ }
} }
// Remove empty month dirs
try { try {
const remaining = fs.readdirSync(monthPath); const remaining = fs.readdirSync(monthPath);
if (remaining.length === 0) { if (remaining.length === 0) {
@ -200,17 +156,16 @@ export function initDailyLog(baseDir: string): void {
fs.mkdirSync(dailyLogDir, { recursive: true }); fs.mkdirSync(dailyLogDir, { recursive: true });
} catch { /* ignore */ } } catch { /* ignore */ }
currentDayKey = getDayKey(); // Attach listener to main logger
logListener = (line: string) => writeToDailyLog(line); logListener = (line: string) => writeToDailyLog(line);
addLogListener(logListener); addLogListener(logListener);
// Initial cleanup
cleanupOldDailyLogs(); cleanupOldDailyLogs();
// Periodic cleanup
cleanupTimer = setInterval(cleanupOldDailyLogs, CLEANUP_CHECK_INTERVAL_MS); cleanupTimer = setInterval(cleanupOldDailyLogs, CLEANUP_CHECK_INTERVAL_MS);
if (cleanupTimer.unref) cleanupTimer.unref(); if (cleanupTimer.unref) cleanupTimer.unref();
process.once("exit", flushSyncOnExit);
} }
export function shutdownDailyLog(): void { export function shutdownDailyLog(): void {
@ -222,11 +177,14 @@ export function shutdownDailyLog(): void {
clearInterval(cleanupTimer); clearInterval(cleanupTimer);
cleanupTimer = null; cleanupTimer = null;
} }
if (flushTimer) { if (currentLogFd !== null) {
clearTimeout(flushTimer); try { fs.closeSync(currentLogFd); } catch { /* ignore */ }
flushTimer = null; currentLogFd = null;
}
if (currentRenameFd !== null) {
try { fs.closeSync(currentRenameFd); } catch { /* ignore */ }
currentRenameFd = null;
} }
flushSyncOnExit();
currentDayKey = ""; currentDayKey = "";
} }

View File

@ -4551,13 +4551,23 @@ export class DownloadManager extends EventEmitter {
active.abortReason = "stop"; active.abortReason = "stop";
active.abortController.abort("stop"); active.abortController.abort("stop");
} }
// Reset all non-finished items to clean "Wartet" / "Paket gestoppt" state // Reset non-finished items. Items that were part of the current run
// (runItemIds) go back to "Wartet" so they are picked up by the next start().
// Items that were NOT in the run set are marked "Gestoppt" so a subsequent
// start() does not accidentally include the entire queue.
const hadRunItems = this.runItemIds.size > 0;
for (const item of Object.values(this.session.items)) { for (const item of Object.values(this.session.items)) {
if (!isFinishedStatus(item.status)) { if (!isFinishedStatus(item.status)) {
item.status = "queued";
item.speedBps = 0;
const pkg = this.session.packages[item.packageId]; const pkg = this.session.packages[item.packageId];
item.fullStatus = pkg && !pkg.enabled ? "Paket gestoppt" : "Wartet"; const wasInRun = !hadRunItems || this.runItemIds.has(item.id);
if (wasInRun) {
item.status = "queued";
item.fullStatus = pkg && !pkg.enabled ? "Paket gestoppt" : "Wartet";
} else {
item.status = "cancelled";
item.fullStatus = "Gestoppt";
}
item.speedBps = 0;
item.updatedAt = nowMs(); item.updatedAt = nowMs();
} }
} }