fix: align live speed and split package reset

This commit is contained in:
Sucukdeluxe
2026-08-31 18:21:58 +02:00
parent 217b7b026e
commit f14d166898
9 changed files with 270 additions and 85 deletions
+11
View File
@@ -295,6 +295,17 @@ describe("desktop shell", () => {
expect(menu).not.toContain("ctx-menu-active");
});
it("offers separate package reset actions for errors and the complete package", () => {
const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8");
const menu = source.slice(source.indexOf('ariaLabel="Downloadaktionen"'), source.indexOf('ariaLabel="Priorität"'));
expect(menu).toContain("Nur fehlerhafte Dateien zurücksetzen");
expect(menu).toContain("Gesamtes Paket zurücksetzen");
expect(menu).toContain("getPackageErrorItemIds");
expect(menu).toContain("window.rd.resetItems(errorItemIds)");
expect(menu).toContain("window.rd.resetPackage(id)");
});
it("styles checked columns with a green mark and normal text", () => {
const shellCss = readFileSync(new URL("../src/renderer/shell/shell.css", import.meta.url), "utf8");
+97
View File
@@ -49,6 +49,33 @@ describe("runWithLimitedConcurrency", () => {
});
describe("download live update cadence", () => {
it("uses the same rolling traffic window for item and package speed", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-speed-window-"));
tempDirs.push(root);
const manager = new DownloadManager(
defaultSettings(),
emptySession(),
createStoragePaths(path.join(root, "state"))
);
manager.addPackages([{ name: "speed", links: ["https://dummy/speed.bin"] }]);
const initial = manager.getSnapshot();
const packageId = initial.session.packageOrder[0];
const itemId = initial.session.packages[packageId].itemIds[0];
const internal = manager as any;
internal.session.running = true;
internal.session.paused = false;
internal.session.packages[packageId].status = "downloading";
internal.session.items[itemId].status = "downloading";
internal.session.items[itemId].speedBps = 47_870_000;
internal.recordSpeed(18_550_000, packageId, itemId);
const snapshot = manager.getSnapshot();
expect(snapshot.packageSpeedBps[packageId]).toBe(18_550_000);
expect(snapshot.session.items[itemId].speedBps).toBe(snapshot.packageSpeedBps[packageId]);
expect(internal.session.items[itemId].speedBps).toBe(47_870_000);
});
it.each([69, 661, 2_470])("emits a running queue snapshot no sooner than 750 ms for %i items", async (itemCount) => {
vi.useFakeTimers();
try {
@@ -8858,6 +8885,76 @@ describe("download manager", () => {
}
});
it("resets only selected package errors and preserves successful downloaded files", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-selective-package-reset-"));
tempDirs.push(root);
const session = emptySession();
const packageId = "selective-reset-package";
const outputDir = path.join(root, "downloads", "selective-reset");
const createdAt = Date.now();
const itemIds = ["successful", "download-error", "extract-error"];
fs.mkdirSync(outputDir, { recursive: true });
session.packageOrder = [packageId];
session.packages[packageId] = {
id: packageId,
name: "selective-reset",
outputDir,
extractDir: path.join(root, "extract", "selective-reset"),
status: "failed",
itemIds,
cancelled: false,
enabled: true,
createdAt,
updatedAt: createdAt
};
for (const itemId of itemIds) {
const targetPath = path.join(outputDir, `${itemId}.rar`);
fs.writeFileSync(targetPath, itemId);
session.items[itemId] = {
id: itemId,
packageId,
url: `https://dummy/${itemId}`,
provider: "realdebrid",
status: itemId === "download-error" ? "failed" : "completed",
retries: 0,
speedBps: 0,
downloadedBytes: itemId.length,
totalBytes: itemId.length,
progressPercent: 100,
fileName: `${itemId}.rar`,
targetPath,
resumable: true,
attempts: 1,
lastError: itemId === "successful" ? "" : "Fehler",
fullStatus: itemId === "successful" ? "Entpackt" : itemId === "extract-error" ? "Entpack-Fehler: CRCERROR" : "Fehlgeschlagen",
createdAt,
updatedAt: createdAt
};
}
const manager = new DownloadManager(
defaultSettings(),
session,
createStoragePaths(path.join(root, "state"))
);
await manager.resetItems(["download-error", "extract-error"]);
const snapshot = manager.getSnapshot().session;
expect(snapshot.items.successful.status).toBe("completed");
expect(snapshot.items.successful.targetPath).toBe(path.join(outputDir, "successful.rar"));
expect(fs.existsSync(snapshot.items.successful.targetPath)).toBe(true);
for (const itemId of ["download-error", "extract-error"]) {
expect(snapshot.items[itemId]).toEqual(expect.objectContaining({
status: "queued",
downloadedBytes: 0,
totalBytes: null,
targetPath: "",
fullStatus: "Wartet"
}));
expect(fs.existsSync(path.join(outputDir, `${itemId}.rar`))).toBe(false);
}
});
it("does not freeze the scheduler when a reset item's old task is parked in a non-abort-observing await", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root);
+17
View File
@@ -17,6 +17,8 @@ import {
getRemainingDownloadBytes,
getPendingDownloadItemCount,
getDownloadSpeedBps,
getPackageErrorItemIds,
isDownloadItemError,
buildDownloadLogicalRows,
type DownloadSidebarFilter,
type DownloadsModelInput
@@ -888,6 +890,21 @@ describe("downloads model", () => {
expect(getDownloadSpeedBps({ stale: -1, invalid: Number.NaN })).toBe(0);
});
it("selects only failed downloads and extraction errors for a package error reset", () => {
const packageEntry = pkg("package-a", "Reset", ["successful", "download-error", "extract-error"]);
const items = {
successful: item("successful", packageEntry.id, "completed", { fullStatus: "Entpackt" }),
"download-error": item("download-error", packageEntry.id, "failed", { fullStatus: "Fehlgeschlagen nach 3 Versuchen" }),
"extract-error": item("extract-error", packageEntry.id, "completed", { fullStatus: "Entpack-Fehler: CRCERROR" })
};
expect(isDownloadItemError(items.successful)).toBe(false);
expect(getPackageErrorItemIds([packageEntry.id], { [packageEntry.id]: packageEntry }, items)).toEqual([
"download-error",
"extract-error"
]);
});
it("exports the sidebar filter contract used by the downloads shell", () => {
const filter: DownloadSidebarFilter = "queued";
+4 -1
View File
@@ -185,7 +185,10 @@ describe("renderer localization", () => {
it.each([
["Entpackte Downloads sind ausgeblendet", "Extracted downloads are hidden"],
["Deaktiviere „Entpackte Einträge ausblenden“, um sie wieder anzuzeigen.", "Disable “Hide extracted entries” to show them again."],
["Downloads können derzeit nicht gestartet werden", "Downloads cannot be started right now"]
["Downloads können derzeit nicht gestartet werden", "Downloads cannot be started right now"],
["Nur fehlerhafte Dateien zurücksetzen", "Reset failed files only"],
["Gesamtes Paket zurücksetzen", "Reset entire package"],
["Ausgewählte Pakete vollständig zurücksetzen", "Reset selected packages completely"]
])("translates download state text %s in both directions", (german, english) => {
expect(translateUiText(german, "en")).toBe(english);
expect(translateUiText(english, "de")).toBe(german);