Add package and item link export

This commit is contained in:
Sucukdeluxe
2026-03-09 04:11:18 +01:00
parent 7503611893
commit 85a9a2fa9f
15 changed files with 598 additions and 39 deletions
+34
View File
@@ -6890,6 +6890,40 @@ describe("download manager", () => {
expect(() => manager.importQueue("{not-json")).toThrow(/Ungultige Queue-Datei/i);
});
it("imports structured text exports and preserves package names and file hints", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root);
const manager = new DownloadManager(
{
...defaultSettings(),
token: "rd-token",
outputDir: path.join(root, "downloads"),
extractDir: path.join(root, "extract")
},
emptySession(),
createStoragePaths(path.join(root, "state"))
);
const result = manager.importQueue([
"# rd-link-export: 1",
"# package: Dave Staffel 1",
"# file: Dave.S01E01.rar",
"https://example.com/e01",
"# file: Dave.S01E02.rar",
"https://example.com/e02"
].join("\n"));
expect(result).toEqual({ addedPackages: 1, addedLinks: 2 });
const snapshot = manager.getSnapshot();
const packageId = snapshot.session.packageOrder[0];
const pkg = snapshot.session.packages[packageId];
expect(pkg?.name).toBe("Dave Staffel 1");
const importedItems = pkg?.itemIds.map((itemId) => snapshot.session.items[itemId]);
expect(importedItems?.map((item) => item?.fileName)).toEqual(["Dave.S01E01.rar", "Dave.S01E02.rar"]);
});
it("applies global speed limit path when global mode is enabled", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root);
+149
View File
@@ -0,0 +1,149 @@
import { describe, expect, it } from "vitest";
import { buildLinkExportSelection, serializeLinkExportText } from "../src/main/link-export";
import { parseCollectorInput } from "../src/main/link-parser";
import type { UiSnapshot } from "../src/shared/types";
function buildSnapshot(): UiSnapshot {
return {
settings: {} as UiSnapshot["settings"],
session: {
version: 1,
packageOrder: ["pkg-1", "pkg-2"],
packages: {
"pkg-1": {
id: "pkg-1",
name: "Dave Staffel 1",
outputDir: "C:\\Downloads\\Dave Staffel 1",
extractDir: "C:\\Extract\\Dave Staffel 1",
status: "queued",
itemIds: ["item-1", "item-2"],
cancelled: false,
enabled: true,
priority: "normal",
createdAt: 1,
updatedAt: 1
},
"pkg-2": {
id: "pkg-2",
name: "Andere Staffel",
outputDir: "C:\\Downloads\\Andere Staffel",
extractDir: "C:\\Extract\\Andere Staffel",
status: "queued",
itemIds: ["item-3"],
cancelled: false,
enabled: true,
priority: "normal",
createdAt: 1,
updatedAt: 1
}
},
items: {
"item-1": {
id: "item-1",
packageId: "pkg-1",
url: "https://example.com/e01",
provider: null,
status: "queued",
retries: 0,
speedBps: 0,
downloadedBytes: 0,
totalBytes: null,
progressPercent: 0,
fileName: "Dave.S01E01.rar",
targetPath: "",
resumable: true,
attempts: 0,
lastError: "",
fullStatus: "Wartet",
createdAt: 1,
updatedAt: 1
},
"item-2": {
id: "item-2",
packageId: "pkg-1",
url: "https://example.com/e02",
provider: null,
status: "queued",
retries: 0,
speedBps: 0,
downloadedBytes: 0,
totalBytes: null,
progressPercent: 0,
fileName: "Dave.S01E02.rar",
targetPath: "",
resumable: true,
attempts: 0,
lastError: "",
fullStatus: "Wartet",
createdAt: 1,
updatedAt: 1
},
"item-3": {
id: "item-3",
packageId: "pkg-2",
url: "https://example.com/other",
provider: null,
status: "queued",
retries: 0,
speedBps: 0,
downloadedBytes: 0,
totalBytes: null,
progressPercent: 0,
fileName: "Andere.S01E01.rar",
targetPath: "",
resumable: true,
attempts: 0,
lastError: "",
fullStatus: "Wartet",
createdAt: 1,
updatedAt: 1
}
},
runStartedAt: 0,
totalDownloadedBytes: 0,
summaryText: "",
reconnectUntil: 0,
reconnectReason: "",
paused: false,
running: false,
updatedAt: 1
},
summary: null,
stats: {
totalDownloaded: 0,
totalDownloadedAllTime: 0,
totalFilesSession: 0,
totalFilesAllTime: 0,
totalPackages: 2,
sessionStartedAt: 0
},
speedText: "",
etaText: "",
canStart: true,
canStop: false,
canPause: false,
clipboardActive: false,
reconnectSeconds: 0,
packageSpeedBps: {}
};
}
describe("link-export", () => {
it("keeps original package names when exporting selected items", () => {
const selection = buildLinkExportSelection(buildSnapshot(), [], ["item-1", "item-3"]);
expect(selection.packageCount).toBe(2);
expect(selection.linkCount).toBe(2);
expect(selection.packages.map((pkg) => pkg.name)).toEqual(["Dave Staffel 1", "Andere Staffel"]);
});
it("roundtrips exported text back into parsed package inputs", () => {
const selection = buildLinkExportSelection(buildSnapshot(), [], ["item-1", "item-2"]);
const text = serializeLinkExportText(selection.packages);
const reparsed = parseCollectorInput(text, "");
expect(reparsed).toHaveLength(1);
expect(reparsed[0]?.name).toBe("Dave Staffel 1");
expect(reparsed[0]?.links).toEqual(["https://example.com/e01", "https://example.com/e02"]);
expect(reparsed[0]?.fileNames).toEqual(["Dave.S01E01.rar", "Dave.S01E02.rar"]);
});
});
+12
View File
@@ -33,6 +33,18 @@ describe("link-parser", () => {
// "Valid?Name*" becomes "Valid Name " -> trimmed to "Valid Name"
expect(result.map(p => p.name).sort()).toEqual(["Valid Name", "Valid_Name"]);
});
it("preserves file name hints when merging packages", () => {
const input = [
{ name: "Package A", links: ["http://link1", "http://link2"], fileNames: ["one.rar", "two.rar"] },
{ name: "Package A", links: ["http://link3", "http://link1"], fileNames: ["three.rar", "ignored.rar"] }
];
const result = mergePackageInputs(input);
expect(result).toHaveLength(1);
expect(result[0]?.links).toEqual(["http://link1", "http://link2", "http://link3"]);
expect(result[0]?.fileNames).toEqual(["one.rar", "two.rar", "three.rar"]);
});
});
describe("parseCollectorInput", () => {
+22
View File
@@ -40,6 +40,28 @@ describe("utils", () => {
expect(parsed[1].name).toBe("B");
});
it("parses optional file markers for roundtrip imports", () => {
const parsed = parsePackagesFromLinksText(
"# rd-link-export: 1\n# package: Dave Staffel 1\n# file: Folge 001.rar\nhttps://a.com/1\n# file: Folge 002.rar\nhttps://a.com/2\n",
"Default"
);
expect(parsed).toHaveLength(1);
expect(parsed[0].name).toBe("Dave Staffel 1");
expect(parsed[0].links).toEqual(["https://a.com/1", "https://a.com/2"]);
expect(parsed[0].fileNames).toEqual(["Folge 001.rar", "Folge 002.rar"]);
});
it("does not carry a file marker across package boundaries", () => {
const parsed = parsePackagesFromLinksText(
"# package: Dave Staffel 1\n# file: Folge 001.rar\n# package: Dave Staffel 2\nhttps://a.com/2\n",
"Default"
);
expect(parsed).toHaveLength(1);
expect(parsed[0].name).toBe("Dave Staffel 2");
expect(parsed[0].links).toEqual(["https://a.com/2"]);
expect(parsed[0].fileNames).toBeUndefined();
});
it("formats eta", () => {
expect(formatEta(-1)).toBe("--");
expect(formatEta(65)).toBe("01:05");