Release v1.4.19 with 4SF/4SJ auto-rename support
Some checks are pending
Build and Release / build (push) Waiting to run
Some checks are pending
Build and Release / build (push) Waiting to run
This commit is contained in:
parent
b971a79047
commit
556f0672dc
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.4.16",
|
"version": "1.4.19",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.4.16",
|
"version": "1.4.19",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.4.18",
|
"version": "1.4.19",
|
||||||
"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",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -k \"npm:dev:main:watch\" \"npm:dev:renderer\" \"npm:dev:electron\"",
|
"dev": "concurrently -k \"npm:dev:main:watch\" \"npm:dev:renderer\" \"npm:dev:electron\"",
|
||||||
"dev:renderer": "vite",
|
"dev:renderer": "vite",
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export function defaultSettings(): AppSettings {
|
|||||||
outputDir: baseDir,
|
outputDir: baseDir,
|
||||||
packageName: "",
|
packageName: "",
|
||||||
autoExtract: true,
|
autoExtract: true,
|
||||||
|
autoRename4sf4sj: false,
|
||||||
extractDir: path.join(baseDir, "_entpackt"),
|
extractDir: path.join(baseDir, "_entpackt"),
|
||||||
createExtractSubfolder: true,
|
createExtractSubfolder: true,
|
||||||
hybridExtract: true,
|
hybridExtract: true,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
StartConflictResolutionResult,
|
StartConflictResolutionResult,
|
||||||
UiSnapshot
|
UiSnapshot
|
||||||
} from "../shared/types";
|
} from "../shared/types";
|
||||||
import { REQUEST_RETRIES } from "./constants";
|
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS } from "./constants";
|
||||||
import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
|
import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
|
||||||
import { DebridService, MegaWebUnrestrictor } from "./debrid";
|
import { DebridService, MegaWebUnrestrictor } from "./debrid";
|
||||||
import { collectArchiveCleanupTargets, extractPackageArchives } from "./extractor";
|
import { collectArchiveCleanupTargets, extractPackageArchives } from "./extractor";
|
||||||
@ -194,6 +194,95 @@ function isPathInsideDir(filePath: string, dirPath: string): boolean {
|
|||||||
return file.startsWith(withSep);
|
return file.startsWith(withSep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SCENE_RELEASE_FOLDER_RE = /-(?:4sf|4sj)$/i;
|
||||||
|
const SCENE_EPISODE_RE = /(?:^|[._\-\s])s(\d{1,2})e(\d{1,3})(?:[._\-\s]|$)/i;
|
||||||
|
const SCENE_SEASON_ONLY_RE = /(^|[._\-\s])s\d{1,2}(?=[._\-\s]|$)/i;
|
||||||
|
const SCENE_RP_TOKEN_RE = /(?:^|[._\-\s])rp(?:[._\-\s]|$)/i;
|
||||||
|
const SCENE_REPACK_TOKEN_RE = /(?:^|[._\-\s])repack(?:[._\-\s]|$)/i;
|
||||||
|
const SCENE_QUALITY_TOKEN_RE = /([._\-\s])((?:4320|2160|1440|1080|720|576|540|480|360)p)(?=[._\-\s]|$)/i;
|
||||||
|
|
||||||
|
function extractEpisodeToken(fileName: string): string | null {
|
||||||
|
const match = String(fileName || "").match(SCENE_EPISODE_RE);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const season = Number(match[1]);
|
||||||
|
const episode = Number(match[2]);
|
||||||
|
if (!Number.isFinite(season) || !Number.isFinite(episode) || season < 0 || episode < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `S${String(season).padStart(2, "0")}E${String(episode).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEpisodeTokenToFolderName(folderName: string, episodeToken: string): string {
|
||||||
|
const trimmed = String(folderName || "").trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return episodeToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withEpisode = trimmed.replace(
|
||||||
|
/(^|[._\-\s])s\d{1,2}e\d{1,3}(?=[._\-\s]|$)/i,
|
||||||
|
`$1${episodeToken}`
|
||||||
|
);
|
||||||
|
if (withEpisode !== trimmed) {
|
||||||
|
return withEpisode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withSeason = trimmed.replace(SCENE_SEASON_ONLY_RE, `$1${episodeToken}`);
|
||||||
|
if (withSeason !== trimmed) {
|
||||||
|
return withSeason;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withSuffixInsert = trimmed.replace(/-(4sf|4sj)$/i, `.${episodeToken}-$1`);
|
||||||
|
if (withSuffixInsert !== trimmed) {
|
||||||
|
return withSuffixInsert;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${trimmed}.${episodeToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceHasRpToken(fileName: string): boolean {
|
||||||
|
return SCENE_RP_TOKEN_RE.test(String(fileName || ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureRepackToken(baseName: string): string {
|
||||||
|
if (SCENE_REPACK_TOKEN_RE.test(baseName)) {
|
||||||
|
return baseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withQualityToken = baseName.replace(SCENE_QUALITY_TOKEN_RE, ".REPACK.$2");
|
||||||
|
if (withQualityToken !== baseName) {
|
||||||
|
return withQualityToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withSuffixToken = baseName.replace(/-(4sf|4sj)$/i, ".REPACK-$1");
|
||||||
|
if (withSuffixToken !== baseName) {
|
||||||
|
return withSuffixToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseName}.REPACK`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAutoRenameBaseName(folderName: string, sourceFileName: string): string | null {
|
||||||
|
if (!SCENE_RELEASE_FOLDER_RE.test(folderName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const episodeToken = extractEpisodeToken(sourceFileName);
|
||||||
|
if (!episodeToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = applyEpisodeTokenToFolderName(folderName, episodeToken);
|
||||||
|
if (sourceHasRpToken(sourceFileName)) {
|
||||||
|
next = ensureRepackToken(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitizeFilename(next);
|
||||||
|
}
|
||||||
|
|
||||||
export class DownloadManager extends EventEmitter {
|
export class DownloadManager extends EventEmitter {
|
||||||
private settings: AppSettings;
|
private settings: AppSettings;
|
||||||
|
|
||||||
@ -1015,6 +1104,83 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private collectVideoFiles(rootDir: string): string[] {
|
||||||
|
if (!rootDir || !fs.existsSync(rootDir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: string[] = [];
|
||||||
|
const stack = [rootDir];
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const current = stack.pop() as string;
|
||||||
|
let entries: fs.Dirent[] = [];
|
||||||
|
try {
|
||||||
|
entries = fs.readdirSync(current, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(current, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
stack.push(fullPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const extension = path.extname(entry.name).toLowerCase();
|
||||||
|
if (!SAMPLE_VIDEO_EXTENSIONS.has(extension)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
files.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private autoRenameExtractedVideoFiles(extractDir: string): number {
|
||||||
|
if (!this.settings.autoRename4sf4sj) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoFiles = this.collectVideoFiles(extractDir);
|
||||||
|
let renamed = 0;
|
||||||
|
|
||||||
|
for (const sourcePath of videoFiles) {
|
||||||
|
const sourceName = path.basename(sourcePath);
|
||||||
|
const sourceExt = path.extname(sourceName);
|
||||||
|
const sourceBaseName = path.basename(sourceName, sourceExt);
|
||||||
|
const folderName = path.basename(path.dirname(sourcePath));
|
||||||
|
const targetBaseName = buildAutoRenameBaseName(folderName, sourceBaseName);
|
||||||
|
if (!targetBaseName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = path.join(path.dirname(sourcePath), `${targetBaseName}${sourceExt}`);
|
||||||
|
if (pathKey(targetPath) === pathKey(sourcePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fs.existsSync(targetPath)) {
|
||||||
|
logger.warn(`Auto-Rename übersprungen (Ziel existiert): ${targetPath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.renameSync(sourcePath, targetPath);
|
||||||
|
renamed += 1;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Auto-Rename fehlgeschlagen (${sourceName}): ${compactErrorText(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renamed > 0) {
|
||||||
|
logger.info(`Auto-Rename (4SF/4SJ): ${renamed} Datei(en) umbenannt`);
|
||||||
|
}
|
||||||
|
return renamed;
|
||||||
|
}
|
||||||
|
|
||||||
public cancelPackage(packageId: string): void {
|
public cancelPackage(packageId: string): void {
|
||||||
const pkg = this.session.packages[packageId];
|
const pkg = this.session.packages[packageId];
|
||||||
if (!pkg) {
|
if (!pkg) {
|
||||||
@ -2827,6 +2993,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
pkg.status = "failed";
|
pkg.status = "failed";
|
||||||
} else {
|
} else {
|
||||||
const hasExtractedOutput = this.directoryHasAnyFiles(pkg.extractDir);
|
const hasExtractedOutput = this.directoryHasAnyFiles(pkg.extractDir);
|
||||||
|
if (result.extracted > 0 || hasExtractedOutput) {
|
||||||
|
this.autoRenameExtractedVideoFiles(pkg.extractDir);
|
||||||
|
}
|
||||||
const sourceExists = fs.existsSync(pkg.outputDir);
|
const sourceExists = fs.existsSync(pkg.outputDir);
|
||||||
let finalStatusText = "";
|
let finalStatusText = "";
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
|||||||
outputDir: asText(settings.outputDir) || defaults.outputDir,
|
outputDir: asText(settings.outputDir) || defaults.outputDir,
|
||||||
packageName: asText(settings.packageName),
|
packageName: asText(settings.packageName),
|
||||||
autoExtract: Boolean(settings.autoExtract),
|
autoExtract: Boolean(settings.autoExtract),
|
||||||
|
autoRename4sf4sj: Boolean(settings.autoRename4sf4sj),
|
||||||
extractDir: asText(settings.extractDir) || defaults.extractDir,
|
extractDir: asText(settings.extractDir) || defaults.extractDir,
|
||||||
createExtractSubfolder: Boolean(settings.createExtractSubfolder),
|
createExtractSubfolder: Boolean(settings.createExtractSubfolder),
|
||||||
hybridExtract: Boolean(settings.hybridExtract),
|
hybridExtract: Boolean(settings.hybridExtract),
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const emptySnapshot = (): UiSnapshot => ({
|
|||||||
archivePasswordList: "",
|
archivePasswordList: "",
|
||||||
rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid",
|
rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid",
|
||||||
providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
|
providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
|
||||||
autoExtract: true, extractDir: "", createExtractSubfolder: true, hybridExtract: true,
|
autoExtract: true, autoRename4sf4sj: false, extractDir: "", createExtractSubfolder: true, hybridExtract: true,
|
||||||
cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false,
|
cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false,
|
||||||
removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true,
|
removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true,
|
||||||
autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never",
|
autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never",
|
||||||
@ -971,6 +971,7 @@ export function App(): ReactElement {
|
|||||||
<button className="btn" onClick={() => { void performQuickAction(async () => { const s = await window.rd.pickFolder(); if (s) { setText("extractDir", s); } }); }}>Wählen</button>
|
<button className="btn" onClick={() => { void performQuickAction(async () => { const s = await window.rd.pickFolder(); if (s) { setText("extractDir", s); } }); }}>Wählen</button>
|
||||||
</div>
|
</div>
|
||||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoExtract} onChange={(e) => setBool("autoExtract", e.target.checked)} /> Auto-Extract</label>
|
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoExtract} onChange={(e) => setBool("autoExtract", e.target.checked)} /> Auto-Extract</label>
|
||||||
|
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoRename4sf4sj} onChange={(e) => setBool("autoRename4sf4sj", e.target.checked)} /> Auto-Rename (4SF/4SJ)</label>
|
||||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.hybridExtract} onChange={(e) => setBool("hybridExtract", e.target.checked)} /> Hybrid-Extract</label>
|
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.hybridExtract} onChange={(e) => setBool("hybridExtract", e.target.checked)} /> Hybrid-Extract</label>
|
||||||
<label>Passwortliste (eine Zeile pro Passwort)</label>
|
<label>Passwortliste (eine Zeile pro Passwort)</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export interface AppSettings {
|
|||||||
outputDir: string;
|
outputDir: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
autoExtract: boolean;
|
autoExtract: boolean;
|
||||||
|
autoRename4sf4sj: boolean;
|
||||||
extractDir: string;
|
extractDir: string;
|
||||||
createExtractSubfolder: boolean;
|
createExtractSubfolder: boolean;
|
||||||
hybridExtract: boolean;
|
hybridExtract: boolean;
|
||||||
|
|||||||
@ -30,6 +30,72 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("download manager", () => {
|
describe("download manager", () => {
|
||||||
|
function createCompletedArchiveSession(root: string, packageName: string, extractedFileName: string): {
|
||||||
|
session: ReturnType<typeof emptySession>;
|
||||||
|
packageId: string;
|
||||||
|
itemId: string;
|
||||||
|
outputDir: string;
|
||||||
|
extractDir: string;
|
||||||
|
originalExtractedPath: string;
|
||||||
|
} {
|
||||||
|
const outputDir = path.join(root, "downloads", packageName);
|
||||||
|
const extractDir = path.join(root, "extract", packageName);
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
|
||||||
|
const zip = new AdmZip();
|
||||||
|
zip.addFile(extractedFileName, Buffer.from("video"));
|
||||||
|
const archivePath = path.join(outputDir, "episode.zip");
|
||||||
|
zip.writeZip(archivePath);
|
||||||
|
const archiveSize = fs.statSync(archivePath).size;
|
||||||
|
|
||||||
|
const session = emptySession();
|
||||||
|
const packageId = `${packageName}-pkg`;
|
||||||
|
const itemId = `${packageName}-item`;
|
||||||
|
const createdAt = Date.now() - 20_000;
|
||||||
|
session.packageOrder = [packageId];
|
||||||
|
session.packages[packageId] = {
|
||||||
|
id: packageId,
|
||||||
|
name: packageName,
|
||||||
|
outputDir,
|
||||||
|
extractDir,
|
||||||
|
status: "downloading",
|
||||||
|
itemIds: [itemId],
|
||||||
|
cancelled: false,
|
||||||
|
enabled: true,
|
||||||
|
createdAt,
|
||||||
|
updatedAt: createdAt
|
||||||
|
};
|
||||||
|
session.items[itemId] = {
|
||||||
|
id: itemId,
|
||||||
|
packageId,
|
||||||
|
url: `https://dummy/${packageName}`,
|
||||||
|
provider: "realdebrid",
|
||||||
|
status: "completed",
|
||||||
|
retries: 0,
|
||||||
|
speedBps: 0,
|
||||||
|
downloadedBytes: archiveSize,
|
||||||
|
totalBytes: archiveSize,
|
||||||
|
progressPercent: 100,
|
||||||
|
fileName: "episode.zip",
|
||||||
|
targetPath: archivePath,
|
||||||
|
resumable: true,
|
||||||
|
attempts: 1,
|
||||||
|
lastError: "",
|
||||||
|
fullStatus: "Fertig (100 MB)",
|
||||||
|
createdAt,
|
||||||
|
updatedAt: createdAt
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
packageId,
|
||||||
|
itemId,
|
||||||
|
outputDir,
|
||||||
|
extractDir,
|
||||||
|
originalExtractedPath: path.join(extractDir, extractedFileName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
it("retries interrupted streams and resumes download", async () => {
|
it("retries interrupted streams and resumes download", async () => {
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
tempDirs.push(root);
|
tempDirs.push(root);
|
||||||
@ -3569,4 +3635,125 @@ describe("download manager", () => {
|
|||||||
expect(snapshot.session.packages[packageId]?.status).toBe("completed");
|
expect(snapshot.session.packages[packageId]?.status).toBe("completed");
|
||||||
expect(snapshot.session.items[itemId]?.fullStatus).toBe("Entpackt (Quelle fehlt)");
|
expect(snapshot.session.items[itemId]?.fullStatus).toBe("Entpackt (Quelle fehlt)");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("auto-renames extracted 4SF scene files to folder format", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
|
||||||
|
const packageName = "Asbest.S02.GERMAN.720p.WEB.AVC-4SF";
|
||||||
|
const sourceFileName = "4sf-asbest.web.7p-s02e01.mkv";
|
||||||
|
const expectedFileName = "Asbest.S02E01.GERMAN.720p.WEB.AVC-4SF.mkv";
|
||||||
|
const { session, packageId, itemId, extractDir, originalExtractedPath } = createCompletedArchiveSession(root, packageName, sourceFileName);
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "rd-token",
|
||||||
|
outputDir: path.join(root, "downloads"),
|
||||||
|
extractDir: path.join(root, "extract"),
|
||||||
|
autoExtract: true,
|
||||||
|
autoRename4sf4sj: true,
|
||||||
|
enableIntegrityCheck: false,
|
||||||
|
cleanupMode: "none"
|
||||||
|
},
|
||||||
|
session,
|
||||||
|
createStoragePaths(path.join(root, "state"))
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedPath = path.join(extractDir, expectedFileName);
|
||||||
|
await waitFor(() => fs.existsSync(expectedPath), 12000);
|
||||||
|
const snapshot = manager.getSnapshot();
|
||||||
|
expect(snapshot.session.packages[packageId]?.status).toBe("completed");
|
||||||
|
expect(snapshot.session.items[itemId]?.fullStatus).toBe("Entpackt");
|
||||||
|
expect(fs.existsSync(expectedPath)).toBe(true);
|
||||||
|
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds REPACK marker from rp token and supports 4SJ folders", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
|
||||||
|
const packageName = "Asbest.S02.GERMAN.720p.WEB.AVC-4SJ";
|
||||||
|
const sourceFileName = "4sf-asbest.rp.web.7p-s02e01.mkv";
|
||||||
|
const expectedFileName = "Asbest.S02E01.GERMAN.REPACK.720p.WEB.AVC-4SJ.mkv";
|
||||||
|
const { session, itemId, extractDir, originalExtractedPath } = createCompletedArchiveSession(root, packageName, sourceFileName);
|
||||||
|
|
||||||
|
new DownloadManager(
|
||||||
|
{
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "rd-token",
|
||||||
|
outputDir: path.join(root, "downloads"),
|
||||||
|
extractDir: path.join(root, "extract"),
|
||||||
|
autoExtract: true,
|
||||||
|
autoRename4sf4sj: true,
|
||||||
|
enableIntegrityCheck: false,
|
||||||
|
cleanupMode: "none"
|
||||||
|
},
|
||||||
|
session,
|
||||||
|
createStoragePaths(path.join(root, "state"))
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedPath = path.join(extractDir, expectedFileName);
|
||||||
|
await waitFor(() => fs.existsSync(expectedPath), 12000);
|
||||||
|
expect(fs.existsSync(expectedPath)).toBe(true);
|
||||||
|
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips auto-rename when no SxxExx token exists in source filename", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
|
||||||
|
const packageName = "Asbest.S02.GERMAN.720p.WEB.AVC-4SF";
|
||||||
|
const sourceFileName = "4sf-asbest.rp.web.7p-episode.mkv";
|
||||||
|
const unexpectedName = "Asbest.S02.GERMAN.REPACK.720p.WEB.AVC-4SF.mkv";
|
||||||
|
const { session, itemId, extractDir, originalExtractedPath } = createCompletedArchiveSession(root, packageName, sourceFileName);
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "rd-token",
|
||||||
|
outputDir: path.join(root, "downloads"),
|
||||||
|
extractDir: path.join(root, "extract"),
|
||||||
|
autoExtract: true,
|
||||||
|
autoRename4sf4sj: true,
|
||||||
|
enableIntegrityCheck: false,
|
||||||
|
cleanupMode: "none"
|
||||||
|
},
|
||||||
|
session,
|
||||||
|
createStoragePaths(path.join(root, "state"))
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => manager.getSnapshot().session.items[itemId]?.fullStatus.startsWith("Entpackt"), 12000);
|
||||||
|
expect(fs.existsSync(originalExtractedPath)).toBe(true);
|
||||||
|
expect(fs.existsSync(path.join(extractDir, unexpectedName))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not rename extracted scene files when auto-rename is disabled", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
|
||||||
|
const packageName = "Asbest.S02.GERMAN.720p.WEB.AVC-4SF";
|
||||||
|
const sourceFileName = "4sf-asbest.web.7p-s02e01.mkv";
|
||||||
|
const unexpectedName = "Asbest.S02E01.GERMAN.720p.WEB.AVC-4SF.mkv";
|
||||||
|
const { session, itemId, extractDir, originalExtractedPath } = createCompletedArchiveSession(root, packageName, sourceFileName);
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "rd-token",
|
||||||
|
outputDir: path.join(root, "downloads"),
|
||||||
|
extractDir: path.join(root, "extract"),
|
||||||
|
autoExtract: true,
|
||||||
|
autoRename4sf4sj: false,
|
||||||
|
enableIntegrityCheck: false,
|
||||||
|
cleanupMode: "none"
|
||||||
|
},
|
||||||
|
session,
|
||||||
|
createStoragePaths(path.join(root, "state"))
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => manager.getSnapshot().session.items[itemId]?.fullStatus.startsWith("Entpackt"), 12000);
|
||||||
|
expect(fs.existsSync(originalExtractedPath)).toBe(true);
|
||||||
|
expect(fs.existsSync(path.join(extractDir, unexpectedName))).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user