Compare commits
No commits in common. "a27d0ec8f2c9883615c7b50a7a7200d06529abbe" and "c51b52b86a30197b920d21f8fbebfbe90bd1c7c8" have entirely different histories.
a27d0ec8f2
...
c51b52b86a
@ -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",
|
||||||
|
|||||||
@ -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 = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user