real-debrid-downloader/src/main/cleanup.ts
Sucukdeluxe b96ed1eb7a
Some checks are pending
Build and Release / build (push) Waiting to run
Migrate app to Node Electron with modern React UI
2026-02-27 03:25:56 +01:00

146 lines
4.0 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { ARCHIVE_TEMP_EXTENSIONS, LINK_ARTIFACT_EXTENSIONS, RAR_SPLIT_RE, SAMPLE_DIR_NAMES, SAMPLE_TOKEN_RE, SAMPLE_VIDEO_EXTENSIONS } from "./constants";
export function isArchiveOrTempFile(filePath: string): boolean {
const lower = filePath.toLowerCase();
const ext = path.extname(lower);
if (ARCHIVE_TEMP_EXTENSIONS.has(ext)) {
return true;
}
if (lower.includes(".part") && lower.endsWith(".rar")) {
return true;
}
return RAR_SPLIT_RE.test(lower);
}
export function cleanupCancelledPackageArtifacts(packageDir: string): number {
if (!fs.existsSync(packageDir)) {
return 0;
}
let removed = 0;
const stack = [packageDir];
while (stack.length > 0) {
const current = stack.pop() as string;
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(full);
} else if (entry.isFile() && isArchiveOrTempFile(full)) {
try {
fs.rmSync(full, { force: true });
removed += 1;
} catch {
// ignore
}
}
}
}
return removed;
}
export function removeDownloadLinkArtifacts(extractDir: string): number {
if (!fs.existsSync(extractDir)) {
return 0;
}
let removed = 0;
const stack = [extractDir];
while (stack.length > 0) {
const current = stack.pop() as string;
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(full);
continue;
}
if (!entry.isFile()) {
continue;
}
const ext = path.extname(entry.name).toLowerCase();
const name = entry.name.toLowerCase();
let shouldDelete = LINK_ARTIFACT_EXTENSIONS.has(ext);
if (!shouldDelete && [".txt", ".html", ".htm", ".nfo"].includes(ext)) {
if (/[._\- ](links?|downloads?|urls?|dlc)([._\- ]|$)/i.test(name)) {
try {
const text = fs.readFileSync(full, "utf8");
shouldDelete = /https?:\/\//i.test(text);
} catch {
shouldDelete = false;
}
}
}
if (shouldDelete) {
try {
fs.rmSync(full, { force: true });
removed += 1;
} catch {
// ignore
}
}
}
}
return removed;
}
export function removeSampleArtifacts(extractDir: string): { files: number; dirs: number } {
if (!fs.existsSync(extractDir)) {
return { files: 0, dirs: 0 };
}
let removedFiles = 0;
let removedDirs = 0;
const allDirs: string[] = [];
const stack = [extractDir];
while (stack.length > 0) {
const current = stack.pop() as string;
allDirs.push(current);
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(full);
continue;
}
if (!entry.isFile()) {
continue;
}
const parent = path.basename(path.dirname(full)).toLowerCase();
const stem = path.parse(entry.name).name.toLowerCase();
const ext = path.extname(entry.name).toLowerCase();
const inSampleDir = SAMPLE_DIR_NAMES.has(parent);
const isSampleVideo = SAMPLE_VIDEO_EXTENSIONS.has(ext) && SAMPLE_TOKEN_RE.test(stem);
if (inSampleDir || isSampleVideo) {
try {
fs.rmSync(full, { force: true });
removedFiles += 1;
} catch {
// ignore
}
}
}
}
allDirs.sort((a, b) => b.length - a.length);
for (const dir of allDirs) {
if (dir === extractDir) {
continue;
}
const base = path.basename(dir).toLowerCase();
if (!SAMPLE_DIR_NAMES.has(base)) {
continue;
}
try {
fs.rmSync(dir, { recursive: true, force: true });
removedDirs += 1;
} catch {
// ignore
}
}
return { files: removedFiles, dirs: removedDirs };
}