real-debrid-downloader/src/main/desktop-rename-log.ts
Sucukdeluxe 3ed3877ac9 chore: remove all source code comments and internal artifacts
Strip every comment from the source (parsed with the TypeScript compiler so
strings, template literals, regex literals and JSX are never touched), and drop
internal/working artifacts that do not belong in the public repository
(design mockups, internal analysis docs, a stray backup file and an old log).
No functional change: build is green, the full test suite passes.
2026-06-06 04:53:54 +02:00

253 lines
7.4 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { logTimestamp } from "./log-timestamp";
type DesktopRenameLevel = "INFO" | "WARN" | "ERROR";
const FOLDER_NAME = "Downloader-Log";
let logDir: string | null = null;
let logFilePath: string | null = null;
let sessionHeader = "";
function fileTimestamp(date: Date = new Date()): string {
const pad = (value: number): string => String(value).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_`
+ `${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
}
function sanitizeFieldValue(value: unknown): string {
if (value === undefined || value === null) {
return "";
}
if (typeof value === "string") {
return value.replace(/\r?\n/g, "\\n");
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
try {
return JSON.stringify(value).replace(/\r?\n/g, "\\n");
} catch {
return String(value);
}
}
function formatFields(fields?: Record<string, unknown>): string {
if (!fields) {
return "";
}
const parts = Object.entries(fields)
.filter(([, value]) => value !== undefined && value !== null && sanitizeFieldValue(value) !== "")
.map(([key, value]) => `${key}=${sanitizeFieldValue(value)}`);
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
}
function ensureWritable(): boolean {
if (!logDir || !logFilePath) {
return false;
}
try {
fs.mkdirSync(logDir, { recursive: true });
if (!fs.existsSync(logFilePath)) {
fs.writeFileSync(logFilePath, sessionHeader, "utf8");
}
return true;
} catch {
return false;
}
}
export function initDesktopRenameLog(desktopDir: string | null | undefined): void {
try {
const base = String(desktopDir || "").trim();
if (!base) {
logDir = null;
logFilePath = null;
return;
}
logDir = path.join(base, FOLDER_NAME);
logFilePath = path.join(logDir, `rename-session_${fileTimestamp()}.txt`);
sessionHeader = `=== Rename-Session gestartet: ${logTimestamp()} ===\n`
+ "Diese Datei protokolliert JEDEN Umbenenn-/Verschiebevorgang dieser Programm-Sitzung\n"
+ "und verifiziert nach jedem Vorgang, ob die Datei wirklich unter dem Zielnamen auf der\n"
+ "Platte liegt (und die Quelle verschwunden ist). [INFO]=ok, [ERROR]=Verifikation gescheitert.\n\n";
fs.mkdirSync(logDir, { recursive: true });
fs.writeFileSync(logFilePath, sessionHeader, "utf8");
} catch {
logDir = null;
logFilePath = null;
}
}
export function logDesktopRename(level: DesktopRenameLevel, message: string, fields?: Record<string, unknown>): void {
if (!ensureWritable() || !logFilePath) {
return;
}
try {
fs.appendFileSync(logFilePath, `${logTimestamp()} [${level}] ${message}${formatFields(fields)}\n`, "utf8");
} catch {
}
}
export function getDesktopRenameLogPath(): string | null {
if (!logFilePath) {
return null;
}
try {
return fs.existsSync(logFilePath) ? logFilePath : null;
} catch {
return null;
}
}
export function shutdownDesktopRenameLog(): void {
if (ensureWritable() && logFilePath) {
try {
fs.appendFileSync(logFilePath, `=== Rename-Session beendet: ${logTimestamp()} ===\n`, "utf8");
} catch {
}
}
logDir = null;
logFilePath = null;
}
export interface RenameVerification {
ok: boolean;
level: "INFO" | "WARN" | "ERROR";
targetExists: boolean;
onDiskName: string | null;
nameMatches: boolean;
sourceGone: boolean;
targetSize: number | null;
reason: string;
}
function toLongPath(filePath: string): string {
const absolute = path.resolve(String(filePath || ""));
if (process.platform !== "win32") {
return absolute;
}
if (!absolute || absolute.startsWith("\\\\?\\")) {
return absolute;
}
if (absolute.length < 248) {
return absolute;
}
if (absolute.startsWith("\\\\")) {
return `\\\\?\\UNC\\${absolute.slice(2)}`;
}
return `\\\\?\\${absolute}`;
}
function resolveOnDiskName(requested: string, entries: string[] | null): string | null {
if (entries === null) {
return null;
}
const requestedLower = requested.toLowerCase();
return entries.find((entry) => entry === requested)
|| entries.find((entry) => entry.toLowerCase() === requestedLower)
|| requested;
}
function buildVerification(
sourcePath: string,
targetPath: string,
facts: { targetExists: boolean; targetSize: number | null; dirEntries: string[] | null; sourceExists: boolean }
): RenameVerification {
const requested = path.basename(targetPath);
const dirReadFailed = facts.targetExists && facts.dirEntries === null;
const onDiskName = facts.targetExists ? resolveOnDiskName(requested, facts.dirEntries) : null;
const samePath = path.resolve(sourcePath).toLowerCase() === path.resolve(targetPath).toLowerCase();
const sourceGone = samePath ? true : !facts.sourceExists;
const nameMatches = facts.targetExists && !dirReadFailed && onDiskName === requested;
const problems: string[] = [];
let level: "INFO" | "WARN" | "ERROR" = "INFO";
if (!facts.targetExists) {
problems.push("Zieldatei nach Rename NICHT gefunden");
level = "ERROR";
} else if (!dirReadFailed && !nameMatches) {
problems.push(`On-Disk-Name weicht ab (ist "${onDiskName}", erwartet "${requested}")`);
level = "ERROR";
}
if (!samePath && facts.targetExists && !sourceGone) {
problems.push("Quelldatei existiert noch (moeglicher halb-fertiger Verschiebevorgang)");
level = "ERROR";
}
if (level === "INFO" && dirReadFailed) {
problems.push("Zielverzeichnis nicht lesbar — Schreibweise nicht verifiziert");
level = "WARN";
}
return {
ok: level === "INFO",
level,
targetExists: facts.targetExists,
onDiskName,
nameMatches,
sourceGone,
targetSize: facts.targetSize,
reason: problems.join("; ")
};
}
export function verifyRename(sourcePath: string, targetPath: string): RenameVerification {
const longTarget = toLongPath(targetPath);
let targetExists = false;
let targetSize: number | null = null;
try {
const stat = fs.statSync(longTarget);
targetExists = true;
targetSize = stat.size;
} catch {
targetExists = false;
}
let dirEntries: string[] | null = null;
if (targetExists) {
try {
dirEntries = fs.readdirSync(path.dirname(longTarget));
} catch {
dirEntries = null;
}
}
let sourceExists = false;
try {
fs.statSync(toLongPath(sourcePath));
sourceExists = true;
} catch {
sourceExists = false;
}
return buildVerification(sourcePath, targetPath, { targetExists, targetSize, dirEntries, sourceExists });
}
export async function verifyRenameAsync(sourcePath: string, targetPath: string): Promise<RenameVerification> {
const longTarget = toLongPath(targetPath);
let targetExists = false;
let targetSize: number | null = null;
try {
const stat = await fs.promises.stat(longTarget);
targetExists = true;
targetSize = stat.size;
} catch {
targetExists = false;
}
let dirEntries: string[] | null = null;
if (targetExists) {
try {
dirEntries = await fs.promises.readdir(path.dirname(longTarget));
} catch {
dirEntries = null;
}
}
let sourceExists = false;
try {
await fs.promises.stat(toLongPath(sourcePath));
sourceExists = true;
} catch {
sourceExists = false;
}
return buildVerification(sourcePath, targetPath, { targetExists, targetSize, dirEntries, sourceExists });
}